diff options
Diffstat (limited to 'src/core')
60 files changed, 1476 insertions, 742 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2dd4158e50..bd0e3c5957 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -34,6 +34,7 @@ set(SRCS frontend/camera/interface.cpp frontend/emu_window.cpp frontend/key_map.cpp + frontend/motion_emu.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/applets/applet.cpp @@ -56,7 +57,9 @@ set(SRCS hle/kernel/thread.cpp hle/kernel/timer.cpp hle/kernel/vm_manager.cpp - hle/service/ac_u.cpp + hle/service/ac/ac.cpp + hle/service/ac/ac_i.cpp + hle/service/ac/ac_u.cpp hle/service/act/act.cpp hle/service/act/act_a.cpp hle/service/act/act_u.cpp @@ -208,6 +211,7 @@ set(HEADERS frontend/camera/interface.h frontend/emu_window.h frontend/key_map.h + frontend/motion_emu.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h @@ -233,7 +237,9 @@ set(HEADERS hle/kernel/timer.h hle/kernel/vm_manager.h hle/result.h - hle/service/ac_u.h + hle/service/ac/ac.h + hle/service/ac/ac_i.h + hle/service/ac/ac_u.h hle/service/act/act.h hle/service/act/act_a.h hle/service/act/act_u.h diff --git a/src/core/core.cpp b/src/core/core.cpp index ee52370961..202cd332b0 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -159,6 +159,7 @@ void System::Shutdown() { Kernel::Shutdown(); HW::Shutdown(); CoreTiming::Shutdown(); + cpu_core.reset(); LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/core.h b/src/core/core.h index 1015e8847f..17572a74f1 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -115,7 +115,7 @@ private: static System s_instance; }; -static ARM_Interface& CPU() { +inline ARM_Interface& CPU() { return System::GetInstance().CPU(); } diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index a437d08233..276ecfdf68 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -13,7 +13,7 @@ #include "core/core.h" #include "core/core_timing.h" -int g_clock_rate_arm11 = 268123480; +int g_clock_rate_arm11 = BASE_CLOCK_RATE_ARM11; // is this really necessary? #define INITIAL_SLICE_LENGTH 20000 diff --git a/src/core/core_timing.h b/src/core/core_timing.h index b72a1b5001..d2f85cd4d6 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -21,6 +21,7 @@ // inside callback: // ScheduleEvent(periodInCycles - cycles_late, callback, "whatever") +constexpr int BASE_CLOCK_RATE_ARM11 = 268123480; extern int g_clock_rate_arm11; inline s64 msToCycles(int ms) { diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index 51ce78435d..dd2fb167f0 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -107,6 +107,8 @@ public: case PathParser::NotFound: LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); return ERROR_FILE_NOT_FOUND; + case PathParser::FileFound: + break; // Expected 'success' case } FileUtil::IOFile file(full_path, "r+b"); diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 333dfb92ed..72ff05c65d 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -72,6 +72,8 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& pa FileUtil::CreateEmptyFile(full_path); } break; + case PathParser::FileFound: + break; // Expected 'success' case } FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); @@ -106,6 +108,8 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const { case PathParser::DirectoryFound: LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::FileFound: + break; // Expected 'success' case } if (FileUtil::Delete(full_path)) { @@ -154,6 +158,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou case PathParser::FileFound: LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::DirectoryFound: + break; // Expected 'success' case } if (deleter(full_path)) { @@ -197,6 +203,8 @@ ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (size == 0) { @@ -238,6 +246,8 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const { case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (FileUtil::CreateDir(mount_point + path.AsString())) { @@ -281,6 +291,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Pa case PathParser::FileInPath: LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::DirectoryFound: + break; // Expected 'success' case } auto directory = std::make_unique<DiskDirectory>(full_path); diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp index f2e6a06bc9..f540c4a931 100644 --- a/src/core/file_sys/savedata_archive.cpp +++ b/src/core/file_sys/savedata_archive.cpp @@ -57,6 +57,8 @@ ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& pa FileUtil::CreateEmptyFile(full_path); } break; + case PathParser::FileFound: + break; // Expected 'success' case } FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); @@ -91,6 +93,8 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const { case PathParser::NotFound: LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); return ERROR_FILE_NOT_FOUND; + case PathParser::FileFound: + break; // Expected 'success' case } if (FileUtil::Delete(full_path)) { @@ -139,6 +143,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou case PathParser::FileFound: LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::DirectoryFound: + break; // Expected 'success' case } if (deleter(full_path)) { @@ -182,6 +188,8 @@ ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) cons case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_FILE_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (size == 0) { @@ -225,6 +233,8 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { case PathParser::FileFound: LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); return ERROR_DIRECTORY_ALREADY_EXISTS; + case PathParser::NotFound: + break; // Expected 'success' case } if (FileUtil::CreateDir(mount_point + path.AsString())) { @@ -269,6 +279,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory( case PathParser::FileFound: LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::DirectoryFound: + break; // Expected 'success' case } auto directory = std::make_unique<DiskDirectory>(full_path); diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index f6f90f9e1c..4f0f786ce2 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <cmath> #include "common/assert.h" +#include "common/profiler_reporting.h" #include "core/frontend/emu_window.h" #include "core/frontend/key_map.h" #include "video_core/video_core.h" @@ -89,6 +90,30 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { TouchPressed(framebuffer_x, framebuffer_y); } +void EmuWindow::AccelerometerChanged(float x, float y, float z) { + constexpr float coef = 512; + + std::lock_guard<std::mutex> lock(accel_mutex); + + // TODO(wwylele): do a time stretch as it in GyroscopeChanged + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + accel_x = static_cast<s16>(x * coef); + accel_y = static_cast<s16>(y * coef); + accel_z = static_cast<s16>(z * coef); +} + +void EmuWindow::GyroscopeChanged(float x, float y, float z) { + constexpr float FULL_FPS = 60; + float coef = GetGyroscopeRawToDpsCoefficient(); + float stretch = + FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + std::lock_guard<std::mutex> lock(gyro_mutex); + gyro_x = static_cast<s16>(x * coef * stretch); + gyro_y = static_cast<s16>(y * coef * stretch); + gyro_z = static_cast<s16>(z * coef * stretch); +} + void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; switch (Settings::values.layout_option) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 835c4d5003..1ba64c92b4 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -4,6 +4,7 @@ #pragma once +#include <mutex> #include <tuple> #include <utility> #include "common/common_types.h" @@ -93,6 +94,27 @@ public: void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); /** + * Signal accelerometer state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in unit of g (gravitational acceleration). + * e.g. x = 1.0 means 9.8m/s^2 in x direction. + * @see GetAccelerometerState for axis explanation. + */ + void AccelerometerChanged(float x, float y, float z); + + /** + * Signal gyroscope state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in deg/sec. + * @see GetGyroscopeState for axis explanation. + */ + void GyroscopeChanged(float x, float y, float z); + + /** * Gets the current pad state (which buttons are pressed). * @note This should be called by the core emu thread to get a state set by the window thread. * @note This doesn't include analog input like circle pad direction @@ -134,12 +156,11 @@ public: * 1 unit of return value = 1/512 g (measured by hw test), * where g is the gravitational acceleration (9.8 m/sec2). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Implement accelerometer input in front-end. * @return std::tuple of (x, y, z) */ - std::tuple<s16, s16, s16> GetAccelerometerState() const { - // stubbed - return std::make_tuple(0, -512, 0); + std::tuple<s16, s16, s16> GetAccelerometerState() { + std::lock_guard<std::mutex> lock(accel_mutex); + return std::make_tuple(accel_x, accel_y, accel_z); } /** @@ -153,12 +174,11 @@ public: * 1 unit of return value = (1/coef) deg/sec, * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Implement gyroscope input in front-end. * @return std::tuple of (x, y, z) */ - std::tuple<s16, s16, s16> GetGyroscopeState() const { - // stubbed - return std::make_tuple(0, 0, 0); + std::tuple<s16, s16, s16> GetGyroscopeState() { + std::lock_guard<std::mutex> lock(gyro_mutex); + return std::make_tuple(gyro_x, gyro_y, gyro_z); } /** @@ -216,6 +236,12 @@ protected: circle_pad_x = 0; circle_pad_y = 0; touch_pressed = false; + accel_x = 0; + accel_y = -512; + accel_z = 0; + gyro_x = 0; + gyro_y = 0; + gyro_z = 0; } virtual ~EmuWindow() {} @@ -281,6 +307,16 @@ private: s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) + std::mutex accel_mutex; + s16 accel_x; ///< Accelerometer X-axis value in native 3DS units + s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units + s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units + + std::mutex gyro_mutex; + s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units + s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units + s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units + /** * Clip the provided coordinates to be inside the touchscreen area. */ diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp new file mode 100644 index 0000000000..9a5b3185d2 --- /dev/null +++ b/src/core/frontend/motion_emu.cpp @@ -0,0 +1,89 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/math_util.h" +#include "common/quaternion.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" + +namespace Motion { + +static constexpr int update_millisecond = 100; +static constexpr auto update_duration = + std::chrono::duration_cast<std::chrono::steady_clock::duration>( + std::chrono::milliseconds(update_millisecond)); + +MotionEmu::MotionEmu(EmuWindow& emu_window) + : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {} + +MotionEmu::~MotionEmu() { + if (motion_emu_thread.joinable()) { + shutdown_event.Set(); + motion_emu_thread.join(); + } +} + +void MotionEmu::MotionEmuThread(EmuWindow& emu_window) { + auto update_time = std::chrono::steady_clock::now(); + Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0); + Math::Quaternion<float> old_q; + + while (!shutdown_event.WaitUntil(update_time)) { + update_time += update_duration; + old_q = q; + + { + std::lock_guard<std::mutex> guard(tilt_mutex); + + // Find the quaternion describing current 3DS tilting + q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), + tilt_angle); + } + + auto inv_q = q.Inverse(); + + // Set the gravity vector in world space + auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f); + + // Find the angular rate vector in world space + auto angular_rate = ((q - old_q) * inv_q).xyz * 2; + angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180; + + // Transform the two vectors from world space to 3DS space + gravity = QuaternionRotate(inv_q, gravity); + angular_rate = QuaternionRotate(inv_q, angular_rate); + + // Update the sensor state + emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z); + emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z); + } +} + +void MotionEmu::BeginTilt(int x, int y) { + mouse_origin = Math::MakeVec(x, y); + is_tilting = true; +} + +void MotionEmu::Tilt(int x, int y) { + constexpr float SENSITIVITY = 0.01f; + auto mouse_move = Math::MakeVec(x, y) - mouse_origin; + if (is_tilting) { + std::lock_guard<std::mutex> guard(tilt_mutex); + if (mouse_move.x == 0 && mouse_move.y == 0) { + tilt_angle = 0; + } else { + tilt_direction = mouse_move.Cast<float>(); + tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f, + MathUtil::PI * 0.5f); + } + } +} + +void MotionEmu::EndTilt() { + std::lock_guard<std::mutex> guard(tilt_mutex); + tilt_angle = 0; + is_tilting = false; +} + +} // namespace Motion diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h new file mode 100644 index 0000000000..99d41a7264 --- /dev/null +++ b/src/core/frontend/motion_emu.h @@ -0,0 +1,52 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common/thread.h" +#include "common/vector_math.h" + +class EmuWindow; + +namespace Motion { + +class MotionEmu final { +public: + MotionEmu(EmuWindow& emu_window); + ~MotionEmu(); + + /** + * Signals that a motion sensor tilt has begun. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void BeginTilt(int x, int y); + + /** + * Signals that a motion sensor tilt is occurring. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void Tilt(int x, int y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void EndTilt(); + +private: + Math::Vec2<int> mouse_origin; + + std::mutex tilt_mutex; + Math::Vec2<float> tilt_direction; + float tilt_angle = 0; + + bool is_tilting = false; + + Common::Event shutdown_event; + std::thread motion_emu_thread; + + void MotionEmuThread(EmuWindow& emu_window); +}; + +} // namespace Motion diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index d88e25073a..5cf45ada58 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -57,7 +57,6 @@ const u32 SIGTERM = 15; const u32 MSG_WAITALL = 8; #endif -const u32 R0_REGISTER = 0; const u32 R15_REGISTER = 15; const u32 CPSR_REGISTER = 25; const u32 FPSCR_REGISTER = 58; @@ -816,10 +815,6 @@ static void RemoveBreakpoint() { auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); - start_offset = addr_pos + 1; - u32 len = - HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); - if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints type = BreakpointType::Read; diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp deleted file mode 100644 index d73d98a701..0000000000 --- a/src/core/hle/hle.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/hle/hle.h" -#include "core/hle/service/service.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace { - -bool reschedule; ///< If true, immediately reschedules the CPU to a new thread -} - -namespace HLE { - -void Reschedule(const char* reason) { - DEBUG_ASSERT_MSG(reason != nullptr && strlen(reason) < 256, - "Reschedule: Invalid or too long reason."); - - // TODO(bunnei): It seems that games depend on some CPU execution time elapsing during HLE - // routines. This simulates that time by artificially advancing the number of CPU "ticks". - // The value was chosen empirically, it seems to work well enough for everything tested, but - // is likely not ideal. We should find a more accurate way to simulate timing with HLE. - Core::AppCore().AddTicks(4000); - - Core::AppCore().PrepareReschedule(); - - reschedule = true; -} - -bool IsReschedulePending() { - return reschedule; -} - -void DoneRescheduling() { - reschedule = false; -} - -void Init() { - Service::Init(); - - reschedule = false; - - LOG_DEBUG(Kernel, "initialized OK"); -} - -void Shutdown() { - Service::Shutdown(); - - LOG_DEBUG(Kernel, "shutdown OK"); -} - -} // namespace diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 3e116e3dfc..23f9df0d63 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -22,23 +22,17 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) { evt->reset_type = reset_type; evt->name = std::move(name); - if (reset_type == ResetType::Pulse) { - LOG_ERROR(Kernel, "Unimplemented event reset type Pulse"); - UNIMPLEMENTED(); - } - return evt; } -bool Event::ShouldWait() { +bool Event::ShouldWait(Thread* thread) const { return !signaled; } -void Event::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Event::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Release the event if it's not sticky... - if (reset_type != ResetType::Sticky) + if (reset_type == ResetType::OneShot) signaled = false; } @@ -51,4 +45,11 @@ void Event::Clear() { signaled = false; } +void Event::WakeupAllWaitingThreads() { + WaitObject::WakeupAllWaitingThreads(); + + if (reset_type == ResetType::Pulse) + signaled = false; +} + } // namespace diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index 8dcd23edb8..3e3673508f 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -35,8 +35,10 @@ public: bool signaled; ///< Whether the event has already been signaled std::string name; ///< Name of event (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; void Signal(); void Clear(); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1db8e102f0..f599916f0d 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include <algorithm> -#include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -28,32 +27,39 @@ void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { void WaitObject::RemoveWaitingThread(Thread* thread) { auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); + // If a thread passed multiple handles to the same object, + // the kernel might attempt to remove the thread from the object's + // waiting threads list multiple times. if (itr != waiting_threads.end()) waiting_threads.erase(itr); } SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { - // Remove the threads that are ready or already running from our waitlist - boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) { - return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY || - thread->status == THREADSTATUS_DEAD; - }); - - // TODO(Subv): This call should be performed inside the loop below to check if an object can be - // acquired by a particular thread. This is useful for things like recursive locking of Mutexes. - if (ShouldWait()) - return nullptr; - Thread* candidate = nullptr; s32 candidate_priority = THREADPRIO_LOWEST + 1; for (const auto& thread : waiting_threads) { + // The list of waiting threads must not contain threads that are not waiting to be awakened. + ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + "Inconsistent thread statuses in waiting_threads"); + if (thread->current_priority >= candidate_priority) continue; - bool ready_to_run = - std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), - [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); }); + if (ShouldWait(thread.get())) + continue; + + // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or + // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. + bool ready_to_run = true; + if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { + ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), + [&thread](const SharedPtr<WaitObject>& object) { + return object->ShouldWait(thread.get()); + }); + } + if (ready_to_run) { candidate = thread.get(); candidate_priority = thread->current_priority; @@ -66,7 +72,7 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { void WaitObject::WakeupAllWaitingThreads() { while (auto thread = GetHighestPriorityReadyThread()) { if (!thread->IsSleepingOnWaitAll()) { - Acquire(); + Acquire(thread.get()); // Set the output index of the WaitSynchronizationN call to the index of this object. if (thread->wait_set_output) { thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); @@ -74,18 +80,17 @@ void WaitObject::WakeupAllWaitingThreads() { } } else { for (auto& object : thread->wait_objects) { - object->Acquire(); - object->RemoveWaitingThread(thread.get()); + object->Acquire(thread.get()); } // Note: This case doesn't update the output index of WaitSynchronizationN. - // Clear the thread's waitlist - thread->wait_objects.clear(); } + for (auto& object : thread->wait_objects) + object->RemoveWaitingThread(thread.get()); + thread->wait_objects.clear(); + thread->SetWaitSynchronizationResult(RESULT_SUCCESS); thread->ResumeFromWait(); - // Note: Removing the thread from the object's waitlist will be - // done by GetHighestPriorityReadyThread. } } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 9503e7d044..bb8b99bb5e 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -132,31 +132,32 @@ using SharedPtr = boost::intrusive_ptr<T>; class WaitObject : public Object { public: /** - * Check if the current thread should wait until the object is available + * Check if the specified thread should wait until the object is available + * @param thread The thread about which we're deciding. * @return True if the current thread should wait due to this object being unavailable */ - virtual bool ShouldWait() = 0; + virtual bool ShouldWait(Thread* thread) const = 0; - /// Acquire/lock the object if it is available - virtual void Acquire() = 0; + /// Acquire/lock the object for the specified thread if it is available + virtual void Acquire(Thread* thread) = 0; /** * Add a thread to wait on this object * @param thread Pointer to thread to add */ - void AddWaitingThread(SharedPtr<Thread> thread); + virtual void AddWaitingThread(SharedPtr<Thread> thread); /** * Removes a thread from waiting on this object (e.g. if it was resumed already) * @param thread Pointer to thread to remove */ - void RemoveWaitingThread(Thread* thread); + virtual void RemoveWaitingThread(Thread* thread); /** * Wake up all threads waiting on this object that can be awoken, in priority order, * and set the synchronization result and output of the thread. */ - void WakeupAllWaitingThreads(); + virtual void WakeupAllWaitingThreads(); /// Obtains the highest priority thread that is ready to run from this object's waiting list. SharedPtr<Thread> GetHighestPriorityReadyThread(); diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 736944bae0..cef9612899 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -6,26 +6,18 @@ #include <vector> #include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" +#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/thread.h" namespace Kernel { -/** - * Resumes a thread waiting for the specified mutex - * @param mutex The mutex that some thread is waiting on - */ -static void ResumeWaitingThread(Mutex* mutex) { - // Reset mutex lock thread handle, nothing is waiting - mutex->lock_count = 0; - mutex->holding_thread = nullptr; - mutex->WakeupAllWaitingThreads(); -} - void ReleaseThreadMutexes(Thread* thread) { for (auto& mtx : thread->held_mutexes) { - ResumeWaitingThread(mtx.get()); + mtx->lock_count = 0; + mtx->holding_thread = nullptr; + mtx->WakeupAllWaitingThreads(); } thread->held_mutexes.clear(); } @@ -40,52 +32,74 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { mutex->name = std::move(name); mutex->holding_thread = nullptr; - // Acquire mutex with current thread if initialized as locked... + // Acquire mutex with current thread if initialized as locked if (initial_locked) - mutex->Acquire(); + mutex->Acquire(GetCurrentThread()); return mutex; } -bool Mutex::ShouldWait() { - auto thread = GetCurrentThread(); - bool wait = lock_count > 0 && holding_thread != thread; - - // If the holding thread of the mutex is lower priority than this thread, that thread should - // temporarily inherit this thread's priority - if (wait && thread->current_priority < holding_thread->current_priority) - holding_thread->BoostPriority(thread->current_priority); - - return wait; -} - -void Mutex::Acquire() { - Acquire(GetCurrentThread()); +bool Mutex::ShouldWait(Thread* thread) const { + return lock_count > 0 && thread != holding_thread; } -void Mutex::Acquire(SharedPtr<Thread> thread) { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Mutex::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Actually "acquire" the mutex only if we don't already have it... + // Actually "acquire" the mutex only if we don't already have it if (lock_count == 0) { + priority = thread->current_priority; thread->held_mutexes.insert(this); - holding_thread = std::move(thread); + holding_thread = thread; + thread->UpdatePriority(); + Core::System::GetInstance().PrepareReschedule(); } lock_count++; } void Mutex::Release() { - // Only release if the mutex is held... + // Only release if the mutex is held if (lock_count > 0) { lock_count--; - // Yield to the next thread only if we've fully released the mutex... + // Yield to the next thread only if we've fully released the mutex if (lock_count == 0) { holding_thread->held_mutexes.erase(this); - ResumeWaitingThread(this); + holding_thread->UpdatePriority(); + holding_thread = nullptr; + WakeupAllWaitingThreads(); + Core::System::GetInstance().PrepareReschedule(); } } } +void Mutex::AddWaitingThread(SharedPtr<Thread> thread) { + WaitObject::AddWaitingThread(thread); + thread->pending_mutexes.insert(this); + UpdatePriority(); +} + +void Mutex::RemoveWaitingThread(Thread* thread) { + WaitObject::RemoveWaitingThread(thread); + thread->pending_mutexes.erase(this); + UpdatePriority(); +} + +void Mutex::UpdatePriority() { + if (!holding_thread) + return; + + s32 best_priority = THREADPRIO_LOWEST; + for (auto& waiter : GetWaitingThreads()) { + if (waiter->current_priority < best_priority) + best_priority = waiter->current_priority; + } + + if (best_priority != priority) { + priority = best_priority; + holding_thread->UpdatePriority(); + } +} + } // namespace diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 53c3dc1f1f..c57adf4002 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -35,17 +35,22 @@ public: } int lock_count; ///< Number of times the mutex has been acquired + u32 priority; ///< The priority of the mutex, used for priority inheritance. std::string name; ///< Name of mutex (optional) SharedPtr<Thread> holding_thread; ///< Thread that has acquired the mutex - bool ShouldWait() override; - void Acquire() override; - /** - * Acquires the specified mutex for the specified thread - * @param thread Thread that will acquire the mutex + * Elevate the mutex priority to the best priority + * among the priorities of all its waiting threads. */ - void Acquire(SharedPtr<Thread> thread); + void UpdatePriority(); + + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void AddWaitingThread(SharedPtr<Thread> thread) override; + void RemoveWaitingThread(Thread* thread) override; + void Release(); private: diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 253ab70451..3f51bc5de6 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -62,6 +62,8 @@ s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const { s32 ResourceLimit::GetMaxResourceValue(u32 resource) const { switch (resource) { + case PRIORITY: + return max_priority; case COMMIT: return max_commit; case THREAD: diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index bf76007805..8bda2f75df 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -30,12 +30,13 @@ ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_cou return MakeResult<SharedPtr<Semaphore>>(std::move(semaphore)); } -bool Semaphore::ShouldWait() { +bool Semaphore::ShouldWait(Thread* thread) const { return available_count <= 0; } -void Semaphore::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Semaphore::Acquire(Thread* thread) { + if (available_count <= 0) + return; --available_count; } diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index e01908a257..cde94f7ccd 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -39,8 +39,8 @@ public: s32 available_count; ///< Number of free slots left in the semaphore std::string name; ///< Name of semaphore (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Releases a certain number of slots from a semaphore. diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 6c19aa7c09..fd3bbbcad3 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -14,13 +14,13 @@ namespace Kernel { ServerPort::ServerPort() {} ServerPort::~ServerPort() {} -bool ServerPort::ShouldWait() { +bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.size() == 0; } -void ServerPort::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerPort::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index b0f8df62c5..6f8bdb6a90 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -53,8 +53,8 @@ public: /// ServerSessions created from this port inherit a reference to this handler. std::shared_ptr<Service::SessionRequestHandler> hle_handler; - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; private: ServerPort(); diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 146458c1ca..9447ff236a 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -29,12 +29,12 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create( return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); } -bool ServerSession::ShouldWait() { +bool ServerSession::ShouldWait(Thread* thread) const { return !signaled; } -void ServerSession::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerSession::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); signaled = false; } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 458284a5db..c088b9a199 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -57,9 +57,9 @@ public: */ ResultCode HandleSyncRequest(); - bool ShouldWait() override; + bool ShouldWait(Thread* thread) const override; - void Acquire() override; + void Acquire(Thread* thread) override; std::string name; ///< The name of this session (optional) bool signaled; ///< Whether there's new data available to this ServerSession diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 5fb95dadaa..3b7555d87d 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -27,12 +27,12 @@ namespace Kernel { /// Event type for the thread wake up event static int ThreadWakeupEventType; -bool Thread::ShouldWait() { +bool Thread::ShouldWait(Thread* thread) const { return status != THREADSTATUS_DEAD; } -void Thread::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Thread::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing @@ -66,20 +66,6 @@ Thread* GetCurrentThread() { } /** - * Check if a thread is waiting on the specified wait object - * @param thread The thread to test - * @param wait_object The object to test against - * @return True if the thread is waiting, false otherwise - */ -static bool CheckWait_WaitObject(const Thread* thread, WaitObject* wait_object) { - if (thread->status != THREADSTATUS_WAIT_SYNCH) - return false; - - auto itr = std::find(thread->wait_objects.begin(), thread->wait_objects.end(), wait_object); - return itr != thread->wait_objects.end(); -} - -/** * Check if the specified thread is waiting on the specified address to be arbitrated * @param thread The thread to test * @param wait_address The address to test against @@ -90,9 +76,6 @@ static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) { } void Thread::Stop() { - // Release all the mutexes that this thread holds - ReleaseThreadMutexes(this); - // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); wakeup_callback_handle_table.Close(callback_handle); @@ -114,6 +97,9 @@ void Thread::Stop() { } wait_objects.clear(); + // Release all the mutexes that this thread holds + ReleaseThreadMutexes(this); + // Mark the TLS slot in the thread's page as free. u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; u32 tls_slot = @@ -155,28 +141,6 @@ void ArbitrateAllThreads(u32 address) { } } -/// Boost low priority threads (temporarily) that have been starved -static void PriorityBoostStarvedThreads() { - u64 current_ticks = CoreTiming::GetTicks(); - - for (auto& thread : thread_list) { - // TODO(bunnei): Threads that have been waiting to be scheduled for `boost_ticks` (or - // longer) will have their priority temporarily adjusted to 1 higher than the highest - // priority thread to prevent thread starvation. This general behavior has been verified - // on hardware. However, this is almost certainly not perfect, and the real CTR OS scheduler - // should probably be reversed to verify this. - - const u64 boost_timeout = 2000000; // Boost threads that have been ready for > this long - - u64 delta = current_ticks - thread->last_running_ticks; - - if (thread->status == THREADSTATUS_READY && delta > boost_timeout) { - const s32 priority = std::max(ready_queue.get_first()->current_priority - 1, 0); - thread->BoostPriority(priority); - } - } -} - /** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to @@ -199,8 +163,8 @@ static void SwitchContext(Thread* new_thread) { // Load context of new thread if (new_thread) { - DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, - "Thread must be ready to become running."); + ASSERT_MSG(new_thread->status == THREADSTATUS_READY, + "Thread must be ready to become running."); // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); @@ -210,9 +174,6 @@ static void SwitchContext(Thread* new_thread) { ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; - // Restores thread to its nominal priority if it has been temporarily changed - new_thread->current_priority = new_thread->nominal_priority; - Core::CPU().LoadContext(new_thread->context); Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { @@ -248,14 +209,6 @@ void WaitCurrentThread_Sleep() { thread->status = THREADSTATUS_WAIT_SLEEP; } -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output) { - Thread* thread = GetCurrentThread(); - thread->wait_set_output = wait_set_output; - thread->wait_objects = std::move(wait_objects); - thread->status = THREADSTATUS_WAIT_SYNCH; -} - void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { Thread* thread = GetCurrentThread(); thread->wait_address = wait_address; @@ -281,7 +234,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { return; } - if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { + if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { thread->wait_set_output = false; // Remove the thread from each of its waiting objects' waitlists for (auto& object : thread->wait_objects) @@ -305,8 +259,11 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { } void Thread::ResumeFromWait() { + ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); + switch (status) { - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: case THREADSTATUS_WAIT_ARB: case THREADSTATUS_WAIT_SLEEP: break; @@ -396,14 +353,8 @@ static void ResetThreadContext(ARM_Interface::ThreadContext& context, u32 stack_ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority, u32 arg, s32 processor_id, VAddr stack_top) { - if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { - s32 new_priority = MathUtil::Clamp<s32>(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); - LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", name.c_str(), - priority, new_priority); - // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm - // validity of this - priority = new_priority; - } + ASSERT_MSG(priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST, + "Invalid thread priority"); if (!Memory::IsValidVirtualAddress(entry_point)) { LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); @@ -487,25 +438,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, return MakeResult<SharedPtr<Thread>>(std::move(thread)); } -// TODO(peachum): Remove this. Range checking should be done, and an appropriate error should be -// returned. -static void ClampPriority(const Thread* thread, s32* priority) { - if (*priority < THREADPRIO_HIGHEST || *priority > THREADPRIO_LOWEST) { - DEBUG_ASSERT_MSG( - false, "Application passed an out of range priority. An error should be returned."); - - s32 new_priority = MathUtil::Clamp<s32>(*priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); - LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", - thread->name.c_str(), *priority, new_priority); - // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm - // validity of this - *priority = new_priority; - } -} - void Thread::SetPriority(s32 priority) { - ClampPriority(this, &priority); - + ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, + "Invalid priority value."); // If thread was ready, adjust queues if (status == THREADSTATUS_READY) ready_queue.move(this, current_priority, priority); @@ -515,8 +450,21 @@ void Thread::SetPriority(s32 priority) { nominal_priority = current_priority = priority; } +void Thread::UpdatePriority() { + s32 best_priority = nominal_priority; + for (auto& mutex : held_mutexes) { + if (mutex->priority < best_priority) + best_priority = mutex->priority; + } + BoostPriority(best_priority); +} + void Thread::BoostPriority(s32 priority) { - ready_queue.move(this, current_priority, priority); + // If thread was ready, adjust queues + if (status == THREADSTATUS_READY) + ready_queue.move(this, current_priority, priority); + else + ready_queue.prepare(priority); current_priority = priority; } @@ -538,9 +486,11 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { return thread; } -void Reschedule() { - PriorityBoostStarvedThreads(); +bool HaveReadyThreads() { + return ready_queue.get_first() != nullptr; +} +void Reschedule() { Thread* cur = GetCurrentThread(); Thread* next = PopNextReadyThread(); @@ -563,6 +513,12 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { context.cpu_registers[1] = output; } +s32 Thread::GetWaitObjectIndex(WaitObject* object) const { + ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); + auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); + return std::distance(match, wait_objects.rend()) - 1; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c77ac644d5..c557a22797 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -31,13 +31,14 @@ enum ThreadProcessorId : s32 { }; enum ThreadStatus { - THREADSTATUS_RUNNING, ///< Currently running - THREADSTATUS_READY, ///< Ready to run - THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter - THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC - THREADSTATUS_WAIT_SYNCH, ///< Waiting due to a WaitSynchronization SVC - THREADSTATUS_DORMANT, ///< Created but not yet made ready - THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated + THREADSTATUS_RUNNING, ///< Currently running + THREADSTATUS_READY, ///< Ready to run + THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter + THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC + THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false + THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true + THREADSTATUS_DORMANT, ///< Created but not yet made ready + THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated }; namespace Kernel { @@ -72,8 +73,8 @@ public: return HANDLE_TYPE; } - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Gets the thread's current priority @@ -90,6 +91,12 @@ public: void SetPriority(s32 priority); /** + * Boost's a thread's priority to the best priority among the thread's held mutexes. + * This prevents priority inversion via priority inheritance. + */ + void UpdatePriority(); + + /** * Temporarily boosts the thread's priority until the next time it is scheduled * @param priority The new priority */ @@ -128,13 +135,14 @@ public: /** * Retrieves the index that this particular object occupies in the list of objects - * that the thread passed to WaitSynchronizationN. + * that the thread passed to WaitSynchronizationN, starting the search from the last element. * It is used to set the output value of WaitSynchronizationN when the thread is awakened. + * When a thread wakes up due to an object signal, the kernel will use the index of the last + * matching object in the wait objects list in case of having multiple instances of the same + * object in the list. * @param object Object to query the index of. */ - s32 GetWaitObjectIndex(const WaitObject* object) const { - return wait_objects_index.at(object->GetObjectId()); - } + s32 GetWaitObjectIndex(WaitObject* object) const; /** * Stops a thread, invalidating it from further use @@ -152,10 +160,10 @@ public: /** * Returns whether this thread is waiting for all the objects in * its wait list to become ready, as a result of a WaitSynchronizationN call - * with wait_all = true, or a ReplyAndReceive call. + * with wait_all = true. */ bool IsSleepingOnWaitAll() const { - return !wait_objects.empty(); + return status == THREADSTATUS_WAIT_SYNCH_ALL; } ARM_Interface::ThreadContext context; @@ -178,15 +186,15 @@ public: /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; + /// Mutexes that this thread is currently waiting for. + boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes; + SharedPtr<Process> owner_process; ///< Process that owns this thread - /// Objects that the thread is waiting on. - /// This is only populated when the thread should wait for all the objects to become ready. + /// Objects that the thread is waiting on, in the same order as they were + // passed to WaitSynchronization1/N. std::vector<SharedPtr<WaitObject>> wait_objects; - /// Mapping of Object ids to their position in the last waitlist that this object waited on. - boost::container::flat_map<int, s32> wait_objects_index; - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. @@ -211,6 +219,11 @@ private: SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority); /** + * Returns whether there are any threads that are ready to run. + */ +bool HaveReadyThreads(); + +/** * Reschedules to the next available thread (call after current thread is suspended) */ void Reschedule(); @@ -238,15 +251,6 @@ Thread* GetCurrentThread(); void WaitCurrentThread_Sleep(); /** - * Waits the current thread from a WaitSynchronization call - * @param wait_objects Kernel objects that we are waiting on - * @param wait_set_output If true, set the output parameter on thread wakeup (for - * WaitSynchronizationN only) - */ -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output); - -/** * Waits the current thread from an ArbitrateAddress call * @param wait_address Arbitration address used to resume from wait */ diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index b50cf520df..60537f3550 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -31,20 +31,15 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) { timer->interval_delay = 0; timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom(); - if (reset_type == ResetType::Pulse) { - LOG_ERROR(Kernel, "Unimplemented timer reset type Pulse"); - UNIMPLEMENTED(); - } - return timer; } -bool Timer::ShouldWait() { +bool Timer::ShouldWait(Thread* thread) const { return !signaled; } -void Timer::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Timer::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); if (reset_type == ResetType::OneShot) signaled = false; @@ -70,6 +65,13 @@ void Timer::Clear() { signaled = false; } +void Timer::WakeupAllWaitingThreads() { + WaitObject::WakeupAllWaitingThreads(); + + if (reset_type == ResetType::Pulse) + signaled = false; +} + /// The timer callback event, called when a timer is fired static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index 18ea0236b7..c174f56641 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -39,8 +39,10 @@ public: u64 initial_delay; ///< The delay until the timer fires for the first time u64 interval_delay; ///< The delay until the timer fires after the first time - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; /** * Starts the timer, with the specified initial delay and interval. diff --git a/src/core/hle/service/ac/ac.cpp b/src/core/hle/service/ac/ac.cpp new file mode 100644 index 0000000000..aa270a2c3b --- /dev/null +++ b/src/core/hle/service/ac/ac.cpp @@ -0,0 +1,181 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> + +#include "common/logging/log.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/ac/ac.h" +#include "core/hle/service/ac/ac_i.h" +#include "core/hle/service/ac/ac_u.h" + +namespace Service { +namespace AC { + +struct ACConfig { + std::array<u8, 0x200> data; +}; + +static ACConfig default_config{}; + +static bool ac_connected = false; + +static Kernel::SharedPtr<Kernel::Event> close_event; +static Kernel::SharedPtr<Kernel::Event> connect_event; +static Kernel::SharedPtr<Kernel::Event> disconnect_event; + +void CreateDefaultConfig(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 ac_config_addr = cmd_buff[65]; + + ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), + "Output buffer size not equal ACConfig size"); + + Memory::WriteBlock(ac_config_addr, &default_config, sizeof(ACConfig)); + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void ConnectAsync(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + connect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + if (connect_event) { + connect_event->name = "AC:connect_event"; + connect_event->Signal(); + ac_connected = true; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetConnectResult(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void CloseAsync(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + if (ac_connected && disconnect_event) { + disconnect_event->Signal(); + } + + close_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + if (close_event) { + close_event->name = "AC:close_event"; + close_event->Signal(); + } + + ac_connected = false; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetCloseResult(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetWifiStatus(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + // TODO(purpasmart96): This function is only a stub, + // it returns a valid result without implementing full functionality. + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // Connection type set to none + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void GetInfraPriority(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // Infra Priority, default 0 + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void SetRequestEulaVersion(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 major = cmd_buff[1] & 0xFF; + u32 minor = cmd_buff[2] & 0xFF; + + ASSERT_MSG(cmd_buff[3] == (sizeof(ACConfig) << 14 | 2), + "Input buffer size not equal ACConfig size"); + ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), + "Output buffer size not equal ACConfig size"); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 0; // Infra Priority + + LOG_WARNING(Service_AC, "(STUBBED) called, major=%u, minor=%u", major, minor); +} + +void RegisterDisconnectEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + disconnect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); + if (disconnect_event) { + disconnect_event->name = "AC:disconnect_event"; + } + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void IsConnected(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = ac_connected; + + LOG_WARNING(Service_AC, "(STUBBED) called"); +} + +void SetClientVersion(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const u32 version = cmd_buff[1]; + self->SetVersion(version); + + LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x%08X", version); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error +} + +void Init() { + AddService(new AC_I); + AddService(new AC_U); + + ac_connected = false; + + close_event = nullptr; + connect_event = nullptr; + disconnect_event = nullptr; +} + +void Shutdown() { + ac_connected = false; + + close_event = nullptr; + connect_event = nullptr; + disconnect_event = nullptr; +} + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac.h b/src/core/hle/service/ac/ac.h new file mode 100644 index 0000000000..6185faf9b2 --- /dev/null +++ b/src/core/hle/service/ac/ac.h @@ -0,0 +1,134 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Service { + +class Interface; + +namespace AC { + +/** + * AC::CreateDefaultConfig service function + * Inputs: + * 64 : ACConfig size << 14 | 2 + * 65 : pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void CreateDefaultConfig(Interface* self); + +/** + * AC::ConnectAsync service function + * Inputs: + * 1 : ProcessId Header + * 3 : Copy Handle Header + * 4 : Connection Event handle + * 5 : ACConfig size << 14 | 2 + * 6 : pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void ConnectAsync(Interface* self); + +/** + * AC::GetConnectResult service function + * Inputs: + * 1 : ProcessId Header + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetConnectResult(Interface* self); + +/** + * AC::CloseAsync service function + * Inputs: + * 1 : ProcessId Header + * 3 : Copy Handle Header + * 4 : Event handle, should be signaled when AC connection is closed + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void CloseAsync(Interface* self); + +/** + * AC::GetCloseResult service function + * Inputs: + * 1 : ProcessId Header + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void GetCloseResult(Interface* self); + +/** + * AC::GetWifiStatus service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet. + */ +void GetWifiStatus(Interface* self); + +/** + * AC::GetInfraPriority service function + * Inputs: + * 1 : ACConfig size << 14 | 2 + * 2 : pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Infra Priority + */ +void GetInfraPriority(Interface* self); + +/** + * AC::SetRequestEulaVersion service function + * Inputs: + * 1 : Eula Version major + * 2 : Eula Version minor + * 3 : ACConfig size << 14 | 2 + * 4 : Input pointer to ACConfig struct + * 64 : ACConfig size << 14 | 2 + * 65 : Output pointer to ACConfig struct + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Infra Priority + */ +void SetRequestEulaVersion(Interface* self); + +/** + * AC::RegisterDisconnectEvent service function + * Inputs: + * 1 : ProcessId Header + * 3 : Copy Handle Header + * 4 : Event handle, should be signaled when AC connection is closed + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void RegisterDisconnectEvent(Interface* self); + +/** + * AC::IsConnected service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : bool, is connected + */ +void IsConnected(Interface* self); + +/** + * AC::SetClientVersion service function + * Inputs: + * 1 : Used SDK Version + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void SetClientVersion(Interface* self); + +/// Initialize AC service +void Init(); + +/// Shutdown AC service +void Shutdown(); + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac_i.cpp b/src/core/hle/service/ac/ac_i.cpp new file mode 100644 index 0000000000..b22fe36985 --- /dev/null +++ b/src/core/hle/service/ac/ac_i.cpp @@ -0,0 +1,39 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ac/ac.h" +#include "core/hle/service/ac/ac_i.h" + +namespace Service { +namespace AC { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"}, + {0x00040006, ConnectAsync, "ConnectAsync"}, + {0x00050002, GetConnectResult, "GetConnectResult"}, + {0x00070002, nullptr, "CancelConnectAsync"}, + {0x00080004, CloseAsync, "CloseAsync"}, + {0x00090002, GetCloseResult, "GetCloseResult"}, + {0x000A0000, nullptr, "GetLastErrorCode"}, + {0x000C0000, nullptr, "GetStatus"}, + {0x000D0000, GetWifiStatus, "GetWifiStatus"}, + {0x000E0042, nullptr, "GetCurrentAPInfo"}, + {0x00100042, nullptr, "GetCurrentNZoneInfo"}, + {0x00110042, nullptr, "GetNZoneApNumService"}, + {0x001D0042, nullptr, "ScanAPs"}, + {0x00240042, nullptr, "AddDenyApType"}, + {0x00270002, GetInfraPriority, "GetInfraPriority"}, + {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"}, + {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"}, + {0x003C0042, nullptr, "GetAPSSIDList"}, + {0x003E0042, IsConnected, "IsConnected"}, + {0x00400042, SetClientVersion, "SetClientVersion"}, +}; + +AC_I::AC_I() { + Register(FunctionTable); +} + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac_i.h b/src/core/hle/service/ac/ac_i.h new file mode 100644 index 0000000000..465bba59ce --- /dev/null +++ b/src/core/hle/service/ac/ac_i.h @@ -0,0 +1,22 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service { +namespace AC { + +class AC_I final : public Interface { +public: + AC_I(); + + std::string GetPortName() const override { + return "ac:i"; + } +}; + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac/ac_u.cpp b/src/core/hle/service/ac/ac_u.cpp new file mode 100644 index 0000000000..346671b4aa --- /dev/null +++ b/src/core/hle/service/ac/ac_u.cpp @@ -0,0 +1,39 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/ac/ac.h" +#include "core/hle/service/ac/ac_u.h" + +namespace Service { +namespace AC { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"}, + {0x00040006, ConnectAsync, "ConnectAsync"}, + {0x00050002, GetConnectResult, "GetConnectResult"}, + {0x00070002, nullptr, "CancelConnectAsync"}, + {0x00080004, CloseAsync, "CloseAsync"}, + {0x00090002, GetCloseResult, "GetCloseResult"}, + {0x000A0000, nullptr, "GetLastErrorCode"}, + {0x000C0000, nullptr, "GetStatus"}, + {0x000D0000, GetWifiStatus, "GetWifiStatus"}, + {0x000E0042, nullptr, "GetCurrentAPInfo"}, + {0x00100042, nullptr, "GetCurrentNZoneInfo"}, + {0x00110042, nullptr, "GetNZoneApNumService"}, + {0x001D0042, nullptr, "ScanAPs"}, + {0x00240042, nullptr, "AddDenyApType"}, + {0x00270002, GetInfraPriority, "GetInfraPriority"}, + {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"}, + {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"}, + {0x003C0042, nullptr, "GetAPSSIDList"}, + {0x003E0042, IsConnected, "IsConnected"}, + {0x00400042, SetClientVersion, "SetClientVersion"}, +}; + +AC_U::AC_U() { + Register(FunctionTable); +} + +} // namespace AC +} // namespace Service diff --git a/src/core/hle/service/ac_u.h b/src/core/hle/service/ac/ac_u.h index 573c32d7e6..f9d21e1122 100644 --- a/src/core/hle/service/ac_u.h +++ b/src/core/hle/service/ac/ac_u.h @@ -12,7 +12,6 @@ namespace AC { class AC_U final : public Interface { public: AC_U(); - ~AC_U(); std::string GetPortName() const override { return "ac:u"; diff --git a/src/core/hle/service/ac_u.cpp b/src/core/hle/service/ac_u.cpp deleted file mode 100644 index 36204db4d6..0000000000 --- a/src/core/hle/service/ac_u.cpp +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> - -#include "common/logging/log.h" -#include "core/hle/kernel/event.h" -#include "core/hle/service/ac_u.h" - -namespace Service { -namespace AC { - -struct ACConfig { - std::array<u8, 0x200> data; -}; - -static ACConfig default_config{}; - -static bool ac_connected = false; - -static Kernel::SharedPtr<Kernel::Event> close_event; -static Kernel::SharedPtr<Kernel::Event> connect_event; -static Kernel::SharedPtr<Kernel::Event> disconnect_event; - -/** - * AC_U::CreateDefaultConfig service function - * Inputs: - * 64 : ACConfig size << 14 | 2 - * 65 : pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void CreateDefaultConfig(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - u32 ac_config_addr = cmd_buff[65]; - - ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), - "Output buffer size not equal ACConfig size"); - - Memory::WriteBlock(ac_config_addr, &default_config, sizeof(ACConfig)); - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::ConnectAsync service function - * Inputs: - * 1 : ProcessId Header - * 3 : Copy Handle Header - * 4 : Connection Event handle - * 5 : ACConfig size << 14 | 2 - * 6 : pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void ConnectAsync(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - connect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (connect_event) { - connect_event->name = "AC_U:connect_event"; - connect_event->Signal(); - ac_connected = true; - } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetConnectResult service function - * Inputs: - * 1 : ProcessId Header - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void GetConnectResult(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::CloseAsync service function - * Inputs: - * 1 : ProcessId Header - * 3 : Copy Handle Header - * 4 : Event handle, should be signaled when AC connection is closed - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void CloseAsync(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - if (ac_connected && disconnect_event) { - disconnect_event->Signal(); - } - - close_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (close_event) { - close_event->name = "AC_U:close_event"; - close_event->Signal(); - } - - ac_connected = false; - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetCloseResult service function - * Inputs: - * 1 : ProcessId Header - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void GetCloseResult(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetWifiStatus service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet. - */ -static void GetWifiStatus(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Connection type set to none - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::GetInfraPriority service function - * Inputs: - * 1 : ACConfig size << 14 | 2 - * 2 : pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Infra Priority - */ -static void GetInfraPriority(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Infra Priority, default 0 - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::SetRequestEulaVersion service function - * Inputs: - * 1 : Eula Version major - * 2 : Eula Version minor - * 3 : ACConfig size << 14 | 2 - * 4 : Input pointer to ACConfig struct - * 64 : ACConfig size << 14 | 2 - * 65 : Output pointer to ACConfig struct - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : Infra Priority - */ -static void SetRequestEulaVersion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - u32 major = cmd_buff[1] & 0xFF; - u32 minor = cmd_buff[2] & 0xFF; - - ASSERT_MSG(cmd_buff[3] == (sizeof(ACConfig) << 14 | 2), - "Input buffer size not equal ACConfig size"); - ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2), - "Output buffer size not equal ACConfig size"); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = 0; // Infra Priority - - LOG_WARNING(Service_AC, "(STUBBED) called, major=%u, minor=%u", major, minor); -} - -/** - * AC_U::RegisterDisconnectEvent service function - * Inputs: - * 1 : ProcessId Header - * 3 : Copy Handle Header - * 4 : Event handle, should be signaled when AC connection is closed - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void RegisterDisconnectEvent(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - disconnect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); - if (disconnect_event) { - disconnect_event->name = "AC_U:disconnect_event"; - } - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::IsConnected service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : bool, is connected - */ -static void IsConnected(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = ac_connected; - - LOG_WARNING(Service_AC, "(STUBBED) called"); -} - -/** - * AC_U::SetClientVersion service function - * Inputs: - * 1 : Used SDK Version - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -static void SetClientVersion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - const u32 version = cmd_buff[1]; - self->SetVersion(version); - - LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x%08X", version); - - cmd_buff[1] = RESULT_SUCCESS.raw; // No error -} - -const Interface::FunctionInfo FunctionTable[] = { - {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"}, - {0x00040006, ConnectAsync, "ConnectAsync"}, - {0x00050002, GetConnectResult, "GetConnectResult"}, - {0x00070002, nullptr, "CancelConnectAsync"}, - {0x00080004, CloseAsync, "CloseAsync"}, - {0x00090002, GetCloseResult, "GetCloseResult"}, - {0x000A0000, nullptr, "GetLastErrorCode"}, - {0x000C0000, nullptr, "GetStatus"}, - {0x000D0000, GetWifiStatus, "GetWifiStatus"}, - {0x000E0042, nullptr, "GetCurrentAPInfo"}, - {0x00100042, nullptr, "GetCurrentNZoneInfo"}, - {0x00110042, nullptr, "GetNZoneApNumService"}, - {0x001D0042, nullptr, "ScanAPs"}, - {0x00240042, nullptr, "AddDenyApType"}, - {0x00270002, GetInfraPriority, "GetInfraPriority"}, - {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"}, - {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"}, - {0x003C0042, nullptr, "GetAPSSIDList"}, - {0x003E0042, IsConnected, "IsConnected"}, - {0x00400042, SetClientVersion, "SetClientVersion"}, -}; - -AC_U::AC_U() { - Register(FunctionTable); - - ac_connected = false; - - close_event = nullptr; - connect_event = nullptr; - disconnect_event = nullptr; -} - -AC_U::~AC_U() { - close_event = nullptr; - connect_event = nullptr; - disconnect_event = nullptr; -} - -} // namespace AC -} // namespace Service diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index 6ab16ccd59..e0de037f8e 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "core/hle/service/boss/boss.h" #include "core/hle/service/boss/boss_p.h" #include "core/hle/service/boss/boss_u.h" @@ -33,7 +34,8 @@ void InitializeSession(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param=0x%016X, translation=0x%08X, unk_param4=0x%08X", + LOG_WARNING(Service_BOSS, + "(STUBBED) unk_param=0x%016" PRIX64 ", translation=0x%08X, unk_param4=0x%08X", unk_param, translation, unk_param4); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 65655f45dc..6f13cde274 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -84,7 +84,6 @@ struct ConsoleCountryInfo { static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); } -static const u64 CFG_SAVE_ID = 0x00010017; static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; @@ -115,6 +114,8 @@ static const std::vector<u8> cfg_system_savedata_id = { 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, }; +static u32 preferred_region_code = 0; + void GetCountryCodeString(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 country_code_id = cmd_buff[1]; @@ -160,11 +161,18 @@ void GetCountryCodeID(Service::Interface* self) { cmd_buff[2] = country_code_id; } +static u32 GetRegionValue() { + if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) + return preferred_region_code; + + return Settings::values.region_value; +} + void SecureInfoGetRegion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = Settings::values.region_value; + cmd_buff[2] = GetRegionValue(); } void GenHashConsoleUnique(Service::Interface* self) { @@ -184,7 +192,7 @@ void GetRegionCanadaUSA(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; u8 canada_or_usa = 1; - if (canada_or_usa == Settings::values.region_value) { + if (canada_or_usa == GetRegionValue()) { cmd_buff[2] = 1; } else { cmd_buff[2] = 0; @@ -318,6 +326,7 @@ ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) { void* pointer; CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag)); memcpy(output, pointer, size); + return RESULT_SUCCESS; } @@ -535,10 +544,55 @@ void Init() { AddService(new CFG_U); LoadConfigNANDSaveFile(); + + preferred_region_code = 0; } void Shutdown() {} +/// Checks if the language is available in the chosen region, and returns a proper one +static SystemLanguage AdjustLanguageInfoBlock(u32 region, SystemLanguage language) { + static const std::array<std::vector<SystemLanguage>, 7> region_languages{{ + // JPN + {LANGUAGE_JP}, + // USA + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT}, + // EUR + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, + LANGUAGE_RU}, + // AUS + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, + LANGUAGE_RU}, + // CHN + {LANGUAGE_ZH}, + // KOR + {LANGUAGE_KO}, + // TWN + {LANGUAGE_TW}, + }}; + const auto& available = region_languages[region]; + if (std::find(available.begin(), available.end(), language) == available.end()) { + return available[0]; + } + return language; +} + +void SetPreferredRegionCode(u32 region_code) { + preferred_region_code = region_code; + LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code); + + if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) { + const SystemLanguage current_language = GetSystemLanguage(); + const SystemLanguage adjusted_language = + AdjustLanguageInfoBlock(region_code, current_language); + if (current_language != adjusted_language) { + LOG_WARNING(Service_CFG, "System language %d does not fit the region. Adjusted to %d", + static_cast<int>(current_language), static_cast<int>(adjusted_language)); + SetSystemLanguage(adjusted_language); + } + } +} + void SetUsername(const std::u16string& name) { ASSERT(name.size() <= 10); UsernameBlock block{}; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index fb47c2aa55..618c9647e6 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -282,6 +282,13 @@ void Init(); /// Shutdown the config service void Shutdown(); +/** + * Set the region code preferred by the game so that CFG will adjust to it when the region setting + * is auto. + * @param region_code the preferred region code to set + */ +void SetPreferredRegionCode(u32 region_code); + // Utilities for frontend to set config data. // Note: before calling these functions, LoadConfigNANDSaveFile should be called, // and UpdateConfigNANDSavegame should be called after making changes to config data. diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp index cd0a1a5982..9da55f3282 100644 --- a/src/core/hle/service/err_f.cpp +++ b/src/core/hle/service/err_f.cpp @@ -227,6 +227,8 @@ static void ThrowFatalError(Interface* self) { LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X", errtype.exception_data.exception_info.fpinst2); break; + case ExceptionType::Undefined: + break; // Not logging exception_info for this case } LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str()); break; diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 9479587030..a8c1331ed7 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -149,7 +149,7 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr u32 mask = Memory::Read32(masks_vaddr); // Update the current value of the register only for set mask bits - reg_value = (reg_value & ~mask) | (data | mask); + reg_value = (reg_value & ~mask) | (data & mask); WriteSingleHWReg(base_address, reg_value); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 676154bd41..f14ab38116 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -35,6 +35,15 @@ static u32 next_gyroscope_index; static int enable_accelerometer_count = 0; // positive means enabled static int enable_gyroscope_count = 0; // positive means enabled +static int pad_update_event; +static int accelerometer_update_event; +static int gyroscope_update_event; + +// Updating period for each HID device. These empirical values are measured from a 11.2 3DS. +constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; +constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; +constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; + static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; @@ -65,14 +74,9 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { return state; } -void Update() { +static void UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); - if (mem == nullptr) { - LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); - return; - } - PadState state = VideoCore::g_emu_window->GetPadState(); // Get current circle pad position and update circle pad direction @@ -131,59 +135,68 @@ void Update() { event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); - // Update accelerometer - if (enable_accelerometer_count > 0) { - mem->accelerometer.index = next_accelerometer_index; - next_accelerometer_index = - (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - - AccelerometerDataEntry& accelerometer_entry = - mem->accelerometer.entries[mem->accelerometer.index]; - std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) = - VideoCore::g_emu_window->GetAccelerometerState(); - - // Make up "raw" entry - // TODO(wwylele): - // From hardware testing, the raw_entry values are approximately, - // but not exactly, as twice as corresponding entries (or with a minus sign). - // It may caused by system calibration to the accelerometer. - // Figure out how it works, or, if no game reads raw_entry, - // the following three lines can be removed and leave raw_entry unimplemented. - mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; - mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; - mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; - - // If we just updated index 0, provide a new timestamp - if (mem->accelerometer.index == 0) { - mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; - mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); - } + // Reschedule recurrent event + CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); +} + +static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); + + mem->accelerometer.index = next_accelerometer_index; + next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - event_accelerometer->Signal(); + AccelerometerDataEntry& accelerometer_entry = + mem->accelerometer.entries[mem->accelerometer.index]; + std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) = + VideoCore::g_emu_window->GetAccelerometerState(); + + // Make up "raw" entry + // TODO(wwylele): + // From hardware testing, the raw_entry values are approximately, but not exactly, as twice as + // corresponding entries (or with a minus sign). It may caused by system calibration to the + // accelerometer. Figure out how it works, or, if no game reads raw_entry, the following three + // lines can be removed and leave raw_entry unimplemented. + mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; + mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; + mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; + + // If we just updated index 0, provide a new timestamp + if (mem->accelerometer.index == 0) { + mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; + mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); } - // Update gyroscope - if (enable_gyroscope_count > 0) { - mem->gyroscope.index = next_gyroscope_index; - next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); + event_accelerometer->Signal(); - GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; - std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) = - VideoCore::g_emu_window->GetGyroscopeState(); + // Reschedule recurrent event + CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event); +} - // Make up "raw" entry - mem->gyroscope.raw_entry.x = gyroscope_entry.x; - mem->gyroscope.raw_entry.z = -gyroscope_entry.y; - mem->gyroscope.raw_entry.y = gyroscope_entry.z; +static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); - // If we just updated index 0, provide a new timestamp - if (mem->gyroscope.index == 0) { - mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; - mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); - } + mem->gyroscope.index = next_gyroscope_index; + next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); + + GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; + std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) = + VideoCore::g_emu_window->GetGyroscopeState(); + + // Make up "raw" entry + mem->gyroscope.raw_entry.x = gyroscope_entry.x; + mem->gyroscope.raw_entry.z = -gyroscope_entry.y; + mem->gyroscope.raw_entry.y = gyroscope_entry.z; - event_gyroscope->Signal(); + // If we just updated index 0, provide a new timestamp + if (mem->gyroscope.index == 0) { + mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; + mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); } + + event_gyroscope->Signal(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event); } void GetIPCHandles(Service::Interface* self) { @@ -204,7 +217,11 @@ void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ++enable_accelerometer_count; - event_accelerometer->Signal(); + + // Schedules the accelerometer update event if the accelerometer was just enabled + if (enable_accelerometer_count == 1) { + CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -215,7 +232,11 @@ void DisableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); --enable_accelerometer_count; - event_accelerometer->Signal(); + + // Unschedules the accelerometer update event if the accelerometer was just disabled + if (enable_accelerometer_count == 0) { + CoreTiming::UnscheduleEvent(accelerometer_update_event, 0); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -226,7 +247,11 @@ void EnableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ++enable_gyroscope_count; - event_gyroscope->Signal(); + + // Schedules the gyroscope update event if the gyroscope was just enabled + if (enable_gyroscope_count == 1) { + CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -237,7 +262,11 @@ void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); --enable_gyroscope_count; - event_gyroscope->Signal(); + + // Unschedules the gyroscope update event if the gyroscope was just disabled + if (enable_gyroscope_count == 0) { + CoreTiming::UnscheduleEvent(gyroscope_update_event, 0); + } cmd_buff[1] = RESULT_SUCCESS.raw; @@ -291,6 +320,8 @@ void Init() { next_pad_index = 0; next_touch_index = 0; + next_accelerometer_index = 0; + next_gyroscope_index = 0; // Create event handles event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); @@ -298,6 +329,15 @@ void Init() { event_accelerometer = Event::Create(ResetType::OneShot, "HID:EventAccelerometer"); event_gyroscope = Event::Create(ResetType::OneShot, "HID:EventGyroscope"); event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad"); + + // Register update callbacks + pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback); + accelerometer_update_event = + CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback); + gyroscope_update_event = + CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback); + + CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } void Shutdown() { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7904e73550..21e66dfe07 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -296,9 +296,6 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self); */ void GetGyroscopeLowCalibrateParam(Service::Interface* self); -/// Checks for user input updates -void Update(); - /// Initialize HID service void Init(); diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 4f1dd2fce8..e98388560f 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -93,13 +93,14 @@ static void StartSampling(Interface* self) { sample_rate = static_cast<SampleRate>(cmd_buff[2] & 0xFF); audio_buffer_offset = cmd_buff[3]; audio_buffer_size = cmd_buff[4]; - audio_buffer_loop = static_cast<bool>(cmd_buff[5] & 0xFF); + audio_buffer_loop = (cmd_buff[5] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error is_sampling = true; LOG_WARNING(Service_MIC, "(STUBBED) called, encoding=%u, sample_rate=%u, " "audio_buffer_offset=%d, audio_buffer_size=%u, audio_buffer_loop=%u", - encoding, sample_rate, audio_buffer_offset, audio_buffer_size, audio_buffer_loop); + static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset, + audio_buffer_size, audio_buffer_loop); } /** @@ -114,7 +115,7 @@ static void AdjustSampling(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); sample_rate = static_cast<SampleRate>(cmd_buff[1] & 0xFF); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", sample_rate); + LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", static_cast<u32>(sample_rate)); } /** @@ -201,7 +202,7 @@ static void GetGain(Interface* self) { */ static void SetPower(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - mic_power = static_cast<bool>(cmd_buff[1] & 0xFF); + mic_power = (cmd_buff[1] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_MIC, "(STUBBED) called, mic_power=%u", mic_power); } @@ -251,7 +252,7 @@ static void SetIirFilterMic(Interface* self) { */ static void SetClamp(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - clamp = static_cast<bool>(cmd_buff[1] & 0xFF); + clamp = (cmd_buff[1] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_MIC, "(STUBBED) called, clamp=%u", clamp); } @@ -281,7 +282,7 @@ static void GetClamp(Interface* self) { */ static void SetAllowShellClosed(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - allow_shell_closed = static_cast<bool>(cmd_buff[1] & 0xFF); + allow_shell_closed = (cmd_buff[1] & 0xFF) != 0; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_MIC, "(STUBBED) called, allow_shell_closed=%u", allow_shell_closed); } diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index d9738c6a13..fd3c7d9c22 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" #include "core/hle/service/nfc/nfc_u.h" @@ -9,9 +10,133 @@ namespace Service { namespace NFC { +static Kernel::SharedPtr<Kernel::Event> tag_in_range_event; +static Kernel::SharedPtr<Kernel::Event> tag_out_of_range_event; +static TagState nfc_tag_state = TagState::NotInitialized; +static CommunicationStatus nfc_status = CommunicationStatus::NfcInitialized; + +void Initialize(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u8 param = static_cast<u8>(cmd_buff[1] & 0xFF); + + nfc_tag_state = TagState::NotScanning; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called, param=%u", param); +} + +void Shutdown(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u8 param = static_cast<u8>(cmd_buff[1] & 0xFF); + nfc_tag_state = TagState::NotInitialized; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called, param=%u", param); +} + +void StartCommunication(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void StopCommunication(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void StartTagScanning(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::TagInRange; + tag_in_range_event->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void StopTagScanning(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::NotScanning; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void LoadAmiiboData(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::TagDataLoaded; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void ResetTagScanState(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + nfc_tag_state = TagState::NotScanning; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void GetTagInRangeEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom(); + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void GetTagOutOfRangeEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xC, 1, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).MoveFrom(); + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + +void GetTagState(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u8>(nfc_tag_state); + LOG_DEBUG(Service_NFC, "(STUBBED) called"); +} + +void CommunicationGetStatus(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = static_cast<u8>(nfc_status); + LOG_DEBUG(Service_NFC, "(STUBBED) called"); +} + void Init() { AddService(new NFC_M()); AddService(new NFC_U()); + + tag_in_range_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_in_range_event"); + tag_out_of_range_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_out_range_event"); + nfc_tag_state = TagState::NotInitialized; +} + +void Shutdown() { + tag_in_range_event = nullptr; + tag_out_of_range_event = nullptr; } } // namespace NFC diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index cd65a5fdc1..a013bdae7a 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -4,11 +4,150 @@ #pragma once +#include "common/common_types.h" + namespace Service { + +class Interface; + namespace NFC { +enum class TagState : u8 { + NotInitialized = 0, + NotScanning = 1, + Scanning = 2, + TagInRange = 3, + TagOutOfRange = 4, + TagDataLoaded = 5, +}; + +enum class CommunicationStatus : u8 { + AttemptInitialize = 1, + NfcInitialized = 2, +}; + +/** + * NFC::Initialize service function + * Inputs: + * 0 : Header code [0x00010040] + * 1 : (u8) unknown parameter. Can be either value 0x1 or 0x2 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void Initialize(Interface* self); + +/** + * NFC::Shutdown service function + * Inputs: + * 0 : Header code [0x00020040] + * 1 : (u8) unknown parameter + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void Shutdown(Interface* self); + +/** + * NFC::StartCommunication service function + * Inputs: + * 0 : Header code [0x00030000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StartCommunication(Interface* self); + +/** + * NFC::StopCommunication service function + * Inputs: + * 0 : Header code [0x00040000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StopCommunication(Interface* self); + +/** + * NFC::StartTagScanning service function + * Inputs: + * 0 : Header code [0x00050040] + * 1 : (u16) unknown. This is normally 0x0 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StartTagScanning(Interface* self); + +/** + * NFC::StopTagScanning service function + * Inputs: + * 0 : Header code [0x00060000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void StopTagScanning(Interface* self); + +/** + * NFC::LoadAmiiboData service function + * Inputs: + * 0 : Header code [0x00070000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void LoadAmiiboData(Interface* self); + +/** + * NFC::ResetTagScanState service function + * Inputs: + * 0 : Header code [0x00080000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void ResetTagScanState(Interface* self); + +/** + * NFC::GetTagInRangeEvent service function + * Inputs: + * 0 : Header code [0x000B0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor + * 3 : Event Handle + */ +void GetTagInRangeEvent(Interface* self); + +/** + * NFC::GetTagOutOfRangeEvent service function + * Inputs: + * 0 : Header code [0x000C0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor + * 3 : Event Handle + */ +void GetTagOutOfRangeEvent(Interface* self); + +/** + * NFC::GetTagState service function + * Inputs: + * 0 : Header code [0x000D0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : (u8) Tag state + */ +void GetTagState(Interface* self); + +/** + * NFC::CommunicationGetStatus service function + * Inputs: + * 0 : Header code [0x000F0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : (u8) Communication state + */ +void CommunicationGetStatus(Interface* self); + /// Initialize all NFC services. void Init(); +/// Shutdown all NFC services. +void Shutdown(); + } // namespace NFC } // namespace Service diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp index 717335c119..ebe6376505 100644 --- a/src/core/hle/service/nfc/nfc_m.cpp +++ b/src/core/hle/service/nfc/nfc_m.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" namespace Service { @@ -10,17 +11,19 @@ namespace NFC { const Interface::FunctionInfo FunctionTable[] = { // clang-format off // nfc:u shared commands - {0x00010040, nullptr, "Initialize"}, - {0x00020040, nullptr, "Shutdown"}, - {0x00030000, nullptr, "StartCommunication"}, - {0x00040000, nullptr, "StopCommunication"}, - {0x00050040, nullptr, "StartTagScanning"}, - {0x00060000, nullptr, "StopTagScanning"}, - {0x00070000, nullptr, "LoadAmiiboData"}, - {0x00080000, nullptr, "ResetTagScanState"}, + {0x00010040, Initialize, "Initialize"}, + {0x00020040, Shutdown, "Shutdown"}, + {0x00030000, StartCommunication, "StartCommunication"}, + {0x00040000, StopCommunication, "StopCommunication"}, + {0x00050040, StartTagScanning, "StartTagScanning"}, + {0x00060000, StopTagScanning, "StopTagScanning"}, + {0x00070000, LoadAmiiboData, "LoadAmiiboData"}, + {0x00080000, ResetTagScanState, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, - {0x000D0000, nullptr, "GetTagState"}, - {0x000F0000, nullptr, "CommunicationGetStatus"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, + {0x000C0000, GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"}, + {0x000D0000, GetTagState, "GetTagState"}, + {0x000F0000, CommunicationGetStatus, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, {0x00110000, nullptr, "GetTagInfo"}, {0x00120000, nullptr, "CommunicationGetResult"}, diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp index deffb0b4f8..5a40c78743 100644 --- a/src/core/hle/service/nfc/nfc_u.cpp +++ b/src/core/hle/service/nfc/nfc_u.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_u.h" namespace Service { @@ -9,17 +10,19 @@ namespace NFC { const Interface::FunctionInfo FunctionTable[] = { // clang-format off - {0x00010040, nullptr, "Initialize"}, - {0x00020040, nullptr, "Shutdown"}, - {0x00030000, nullptr, "StartCommunication"}, - {0x00040000, nullptr, "StopCommunication"}, - {0x00050040, nullptr, "StartTagScanning"}, - {0x00060000, nullptr, "StopTagScanning"}, - {0x00070000, nullptr, "LoadAmiiboData"}, - {0x00080000, nullptr, "ResetTagScanState"}, + {0x00010040, Initialize, "Initialize"}, + {0x00020040, Shutdown, "Shutdown"}, + {0x00030000, StartCommunication, "StartCommunication"}, + {0x00040000, StopCommunication, "StopCommunication"}, + {0x00050040, StartTagScanning, "StartTagScanning"}, + {0x00060000, StopTagScanning, "StopTagScanning"}, + {0x00070000, LoadAmiiboData, "LoadAmiiboData"}, + {0x00080000, ResetTagScanState, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, - {0x000D0000, nullptr, "GetTagState"}, - {0x000F0000, nullptr, "CommunicationGetStatus"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, + {0x000C0000, GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"}, + {0x000D0000, GetTagState, "GetTagState"}, + {0x000F0000, CommunicationGetStatus, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, {0x00110000, nullptr, "GetTagInfo"}, {0x00120000, nullptr, "CommunicationGetResult"}, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 7e52a05d94..0672ac2e38 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -6,9 +6,8 @@ #include "common/logging/log.h" #include "common/string_util.h" - #include "core/hle/kernel/server_port.h" -#include "core/hle/service/ac_u.h" +#include "core/hle/service/ac/ac.h" #include "core/hle/service/act/act.h" #include "core/hle/service/am/am.h" #include "core/hle/service/apt/apt.h" @@ -138,6 +137,7 @@ void Init() { AddNamedPort(new ERR::ERR_F); FS::ArchiveInit(); + AC::Init(); ACT::Init(); AM::Init(); APT::Init(); @@ -158,7 +158,6 @@ void Init() { PTM::Init(); QTM::Init(); - AddService(new AC::AC_U); AddService(new CSND::CSND_SND); AddService(new DSP_DSP::Interface); AddService(new GSP::GSP_GPU); @@ -178,6 +177,7 @@ void Init() { /// Shutdown ServiceManager void Shutdown() { PTM::Shutdown(); + NFC::Shutdown(); NIM::Shutdown(); NEWS::Shutdown(); NDM::Shutdown(); @@ -191,6 +191,7 @@ void Shutdown() { BOSS::Shutdown(); APT::Shutdown(); AM::Shutdown(); + AC::Shutdown(); FS::ArchiveShutdown(); g_srv_services.clear(); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index c3918cdd08..dcc5c3c90a 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -603,7 +603,6 @@ static void RecvFrom(Interface* self) { u32 socket_handle = cmd_buffer[1]; u32 len = cmd_buffer[2]; u32 flags = cmd_buffer[3]; - socklen_t addr_len = static_cast<socklen_t>(cmd_buffer[4]); struct { u32 output_buffer_descriptor; @@ -693,7 +692,6 @@ static void Poll(Interface* self) { static void GetSockName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t ctr_len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -734,7 +732,6 @@ static void Shutdown(Interface* self) { static void GetPeerName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -765,7 +762,6 @@ static void Connect(Interface* self) { // performing nonblocking operations and spinlock until the data is available u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_input_addr structure VAddr ctr_input_addr_addr = cmd_buffer[6]; diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index a20194107e..31bb466fc3 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -531,7 +531,9 @@ static void GetStandardCoefficient(Interface* self) { LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u ", index); } else { cmd_buff[0] = IPC::MakeHeader(0x21, 1, 0); - cmd_buff[1] = -1; // TODO(bunnei): Identify the correct error code for this + cmd_buff[1] = ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage) + .raw; LOG_ERROR(Service_Y2R, "called standard_coefficient=%u The argument is invalid!", index); } diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 2ca270de3d..96db39ad94 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -248,6 +248,8 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) { LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); + Core::System::GetInstance().PrepareReschedule(); + // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server // responds and cause a reschedule. return session->SendSyncRequest(); @@ -270,27 +272,27 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); - if (object->ShouldWait()) { + if (object->ShouldWait(thread)) { if (nano_seconds == 0) return ERR_SYNC_TIMEOUT; + thread->wait_objects = {object}; object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread // resumes due to a signal in its wait objects. // Otherwise we retain the default value of timeout. return ERR_SYNC_TIMEOUT; } - object->Acquire(); + object->Acquire(thread); return RESULT_SUCCESS; } @@ -324,19 +326,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha objects[i] = object; } - // Clear the mapping of wait object indices. - // We don't want any lingering state in this map. - // It will be repopulated later in the wait_all = false case. - thread->wait_objects_index.clear(); - if (wait_all) { bool all_available = std::all_of(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + [thread](const ObjectPtr& object) { return !object->ShouldWait(thread); }); if (all_available) { // We can acquire all objects right now, do so. for (auto& object : objects) - object->Acquire(); + object->Acquire(thread); // Note: In this case, the `out` parameter is not set, // and retains whatever value it had before. return RESULT_SUCCESS; @@ -350,22 +347,20 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ALL; // Add the thread to each of the objects' waiting threads. for (auto& object : objects) { object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } - // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN thread->wait_objects = std::move(objects); // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // This value gets set to -1 by default in this case, it is not modified after this. *out = -1; // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to @@ -373,13 +368,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; } else { // Find the first object that is acquirable in the provided list of objects - auto itr = std::find_if(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { + return !object->ShouldWait(thread); + }); if (itr != objects.end()) { // We found a ready object, acquire it and set the result value Kernel::WaitObject* object = itr->get(); - object->Acquire(); + object->Acquire(thread); *out = std::distance(objects.begin(), itr); return RESULT_SUCCESS; } @@ -392,28 +388,24 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; - - // Clear the thread's waitlist, we won't use it for wait_all = false - thread->wait_objects.clear(); + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Add the thread to each of the objects' waiting threads. for (size_t i = 0; i < objects.size(); ++i) { Kernel::WaitObject* object = objects[i].get(); - // Set the index of this object in the mapping of Objects -> index for this thread. - thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i); object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } + thread->wait_objects = std::move(objects); + // Note: If no handles and no timeout were given, then the thread will deadlock, this is // consistent with hardware behavior. // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a // signal in one of its wait objects. // Otherwise we retain the default value of timeout, and -1 in the out parameter @@ -448,6 +440,9 @@ static ResultCode ArbitrateAddress(Kernel::Handle handle, u32 address, u32 type, auto res = arbiter->ArbitrateAddress(static_cast<Kernel::ArbitrationType>(type), address, value, nanoseconds); + // TODO(Subv): Identify in which specific cases this call should cause a reschedule. + Core::System::GetInstance().PrepareReschedule(); + return res; } @@ -537,16 +532,18 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent name = Common::StringFromFormat("unknown-%08x", entry_point); } - // TODO(bunnei): Implement resource limits to return an error code instead of the below assert. - // The error code should be: Description::NotAuthorized, Module::OS, Summary::WrongArgument, - // Level::Permanent - ASSERT_MSG(priority >= THREADPRIO_USERLAND_MAX, "Unexpected thread priority!"); - if (priority > THREADPRIO_LOWEST) { return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } + using Kernel::ResourceLimit; + Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit; + if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) { + return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS, + ErrorSummary::WrongArgument, ErrorLevel::Permanent); + } + switch (processor_id) { case THREADPROCESSORID_ALL: case THREADPROCESSORID_DEFAULT: @@ -574,6 +571,8 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread))); + Core::System::GetInstance().PrepareReschedule(); + LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle); @@ -586,6 +585,7 @@ static void ExitThread() { LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC()); Kernel::ExitCurrentThread(); + Core::System::GetInstance().PrepareReschedule(); } /// Gets the priority for the specified thread @@ -600,11 +600,32 @@ static ResultCode GetThreadPriority(s32* priority, Kernel::Handle handle) { /// Sets the priority for the specified thread static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) { + if (priority > THREADPRIO_LOWEST) { + return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } + SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); if (thread == nullptr) return ERR_INVALID_HANDLE; + using Kernel::ResourceLimit; + // Note: The kernel uses the current process's resource limit instead of + // the one from the thread owner's resource limit. + Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit; + if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) { + return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS, + ErrorSummary::WrongArgument, ErrorLevel::Permanent); + } + thread->SetPriority(priority); + thread->UpdatePriority(); + + // Update the mutexes that this thread is waiting for + for (auto& mutex : thread->pending_mutexes) + mutex->UpdatePriority(); + + Core::System::GetInstance().PrepareReschedule(); return RESULT_SUCCESS; } @@ -844,11 +865,18 @@ static ResultCode CancelTimer(Kernel::Handle handle) { static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); + // Don't attempt to yield execution if there are no available threads to run, + // this way we avoid a useless reschedule to the idle thread. + if (nanoseconds == 0 && !Kernel::HaveReadyThreads()) + return; + // Sleep current thread and check for next thread to schedule Kernel::WaitCurrentThread_Sleep(); // Create an event to wake the thread up after the specified nanosecond delay has passed Kernel::GetCurrentThread()->WakeAfterDelay(nanoseconds); + + Core::System::GetInstance().PrepareReschedule(); } /// This returns the total CPU ticks elapsed since the CPU was powered-on @@ -890,7 +918,11 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); - if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) { + // TODO(Subv): Processes with memory type APPLICATION are not allowed + // to create memory blocks with addr = 0, any attempts to do so + // should return error 0xD92007EA. + if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) && + addr != 0) { return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } @@ -1184,8 +1216,6 @@ void CallSVC(u32 immediate) { if (info) { if (info->func) { info->func(); - // TODO(Subv): Not all service functions should cause a reschedule in all cases. - Core::System::GetInstance().PrepareReschedule(); } else { LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); } diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 1a1ee90b2f..fa8c13d366 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -15,7 +15,6 @@ #include "common/vector_math.h" #include "core/core_timing.h" #include "core/hle/service/gsp_gpu.h" -#include "core/hle/service/hid/hid.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/memory.h" @@ -33,7 +32,7 @@ namespace GPU { Regs g_regs; /// 268MHz CPU clocks / 60Hz frames per second -const u64 frame_ticks = 268123480ull / 60; +const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60; /// Event id for CoreTiming static int vblank_event; /// Total number of frames drawn @@ -551,9 +550,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0); Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1); - // Check for user input updates - Service::HID::Update(); - if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) { FrameLimiter(); } diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 1c10740a0b..09266e8b07 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -177,18 +177,34 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, pos += table.skip; s32 num_patches = table.patch; while (0 < num_patches && pos < end_pos) { - u32 in_addr = - static_cast<u32>(reinterpret_cast<u8*>(pos) - program_image.data()); - u32 addr = TranslateAddr(*pos, &loadinfo, offsets); - LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", - base_addr + in_addr, addr, current_segment_reloc_table, *pos); + u32 in_addr = base_addr + static_cast<u32>(reinterpret_cast<u8*>(pos) - + program_image.data()); + u32 orig_data = *pos; + u32 sub_type = orig_data >> (32 - 4); + u32 addr = TranslateAddr(orig_data & ~0xF0000000, &loadinfo, offsets); + LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", in_addr, addr, + current_segment_reloc_table, *pos); switch (current_segment_reloc_table) { - case 0: - *pos = (addr); + case 0: { + if (sub_type != 0) + return ERROR_READ; + *pos = addr; break; - case 1: - *pos = static_cast<u32>(addr - in_addr); + } + case 1: { + u32 data = addr - in_addr; + switch (sub_type) { + case 0: // 32-bit signed offset + *pos = data; + break; + case 1: // 31-bit signed offset + *pos = data & ~(1U << 31); + break; + default: + return ERROR_READ; + } break; + } default: break; // this should never happen } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 6f21644286..5df33f6d2c 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -11,8 +11,10 @@ #include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "core/loader/ncch.h" +#include "core/loader/smdh.h" #include "core/memory.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -286,7 +288,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); LOG_DEBUG(Loader, "System Mode: %d", - exheader_header.arm11_system_local_caps.system_mode); + static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); @@ -309,6 +311,23 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { return ResultStatus::Success; } +void AppLoader_NCCH::ParseRegionLockoutInfo() { + std::vector<u8> smdh_buffer; + if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { + SMDH smdh; + memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH)); + u32 region_lockout = smdh.region_lockout; + constexpr u32 REGION_COUNT = 7; + for (u32 region = 0; region < REGION_COUNT; ++region) { + if (region_lockout & 1) { + Service::CFG::SetPreferredRegionCode(region); + break; + } + region_lockout >>= 1; + } + } +} + ResultStatus AppLoader_NCCH::Load() { if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; @@ -325,6 +344,9 @@ ResultStatus AppLoader_NCCH::Load() { Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS); + + ParseRegionLockoutInfo(); + return ResultStatus::Success; } diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 6afc171a50..4ef95b5c6d 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -181,7 +181,7 @@ public: * Loads the Exheader and returns the system mode for this application. * @return Optional with the kernel system mode */ - boost::optional<u32> LoadKernelSystemMode(); + boost::optional<u32> LoadKernelSystemMode() override; ResultStatus ReadCode(std::vector<u8>& buffer) override; @@ -229,6 +229,9 @@ private: */ ResultStatus LoadExeFS(); + /// Reads the region lockout info in the SMDH and send it to CFG service + void ParseRegionLockoutInfo(); + bool is_exefs_loaded = false; bool is_compressed = false; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 5d23c52f90..9afaf79ecf 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -20,7 +20,6 @@ void Apply() { VideoCore::g_hw_renderer_enabled = values.use_hw_renderer; VideoCore::g_shader_jit_enabled = values.use_shader_jit; - VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution; VideoCore::g_toggle_framelimit_enabled = values.toggle_framelimit; if (VideoCore::g_emu_window) { diff --git a/src/core/settings.h b/src/core/settings.h index fe47c364fb..b6c75531fc 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -89,7 +89,7 @@ struct Values { // Renderer bool use_hw_renderer; bool use_shader_jit; - bool use_scaled_resolution; + float resolution_factor; bool use_vsync; bool toggle_framelimit; @@ -105,6 +105,7 @@ struct Values { // Audio std::string sink_id; bool enable_audio_stretching; + std::string audio_device_id; // Camera std::array<std::string, Service::CAM::NumCameras> camera_name; @@ -115,5 +116,9 @@ struct Values { u16 gdbstub_port; } extern values; +// a special value for Values::region_value indicating that citra will automatically select a region +// value to fit the region lockout info of the game +static constexpr int REGION_VALUE_AUTO_SELECT = -1; + void Apply(); } |