Merge branch 'yuzu-emu:master' into convert_legacy
This commit is contained in:
commit
e49184e606
|
@ -57,7 +57,7 @@ function(check_submodules_present)
|
||||||
string(REGEX REPLACE "path *= *" "" module ${module})
|
string(REGEX REPLACE "path *= *" "" module ${module})
|
||||||
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
||||||
message(FATAL_ERROR "Git submodule ${module} not found. "
|
message(FATAL_ERROR "Git submodule ${module} not found. "
|
||||||
"Please run: git submodule update --init --recursive")
|
"Please run: \ngit submodule update --init --recursive")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
@ -131,7 +131,7 @@ add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>)
|
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>)
|
||||||
|
|
||||||
# cubeb and boost still make use of deprecated result_of.
|
# boost still makes use of deprecated result_of.
|
||||||
add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
|
add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
@ -167,7 +167,7 @@ macro(yuzu_find_packages)
|
||||||
set(REQUIRED_LIBS
|
set(REQUIRED_LIBS
|
||||||
# Cmake Pkg Prefix Version Conan Pkg
|
# Cmake Pkg Prefix Version Conan Pkg
|
||||||
"Catch2 2.13.7 catch2/2.13.7"
|
"Catch2 2.13.7 catch2/2.13.7"
|
||||||
"fmt 8.0 fmt/8.0.0"
|
"fmt 8.0.1 fmt/8.0.1"
|
||||||
"lz4 1.8 lz4/1.9.2"
|
"lz4 1.8 lz4/1.9.2"
|
||||||
"nlohmann_json 3.8 nlohmann_json/3.8.0"
|
"nlohmann_json 3.8 nlohmann_json/3.8.0"
|
||||||
"ZLIB 1.2 zlib/1.2.11"
|
"ZLIB 1.2 zlib/1.2.11"
|
||||||
|
@ -370,7 +370,7 @@ if (ENABLE_SDL2)
|
||||||
if (YUZU_USE_BUNDLED_SDL2)
|
if (YUZU_USE_BUNDLED_SDL2)
|
||||||
# Detect toolchain and platform
|
# Detect toolchain and platform
|
||||||
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
|
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
|
||||||
set(SDL2_VER "SDL2-2.0.16")
|
set(SDL2_VER "SDL2-2.0.18")
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||||
endif()
|
endif()
|
||||||
|
@ -390,7 +390,7 @@ if (ENABLE_SDL2)
|
||||||
elseif (YUZU_USE_EXTERNAL_SDL2)
|
elseif (YUZU_USE_EXTERNAL_SDL2)
|
||||||
message(STATUS "Using SDL2 from externals.")
|
message(STATUS "Using SDL2 from externals.")
|
||||||
else()
|
else()
|
||||||
find_package(SDL2 2.0.16 REQUIRED)
|
find_package(SDL2 2.0.18 REQUIRED)
|
||||||
|
|
||||||
# Some installations don't set SDL2_LIBRARIES
|
# Some installations don't set SDL2_LIBRARIES
|
||||||
if("${SDL2_LIBRARIES}" STREQUAL "")
|
if("${SDL2_LIBRARIES}" STREQUAL "")
|
||||||
|
|
|
@ -17,7 +17,7 @@ It is written in C++ with portability in mind, and we actively maintain builds f
|
||||||
alt="Azure Mainline CI Build Status">
|
alt="Azure Mainline CI Build Status">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.com/invite/u77vRWY">
|
<a href="https://discord.com/invite/u77vRWY">
|
||||||
<img src="https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white"
|
<img src="https://img.shields.io/discord/398318088170242053?color=5865F2&label=yuzu&logo=discord&logoColor=white"
|
||||||
alt="Discord">
|
alt="Discord">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -44,10 +44,6 @@ target_include_directories(mbedtls PUBLIC ./mbedtls/include)
|
||||||
add_library(microprofile INTERFACE)
|
add_library(microprofile INTERFACE)
|
||||||
target_include_directories(microprofile INTERFACE ./microprofile)
|
target_include_directories(microprofile INTERFACE ./microprofile)
|
||||||
|
|
||||||
# Unicorn
|
|
||||||
add_library(unicorn-headers INTERFACE)
|
|
||||||
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
|
|
||||||
|
|
||||||
# libusb
|
# libusb
|
||||||
if (NOT LIBUSB_FOUND OR YUZU_USE_BUNDLED_LIBUSB)
|
if (NOT LIBUSB_FOUND OR YUZU_USE_BUNDLED_LIBUSB)
|
||||||
add_subdirectory(libusb)
|
add_subdirectory(libusb)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 25f9ed87ff6947d9576fc9d79dee0784e638ac58
|
Subproject commit 2e9821423a237a1206e3c09020778faacfe430be
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1d66483ad2b93f0e00e175f9480c771af90003a7
|
Subproject commit 75d9d125ee655ef80f3bfcd97ae5a805931042b8
|
|
@ -1,18 +0,0 @@
|
||||||
# Exports:
|
|
||||||
# LIBUNICORN_FOUND
|
|
||||||
# LIBUNICORN_INCLUDE_DIR
|
|
||||||
# LIBUNICORN_LIBRARY
|
|
||||||
|
|
||||||
find_path(LIBUNICORN_INCLUDE_DIR
|
|
||||||
unicorn/unicorn.h
|
|
||||||
HINTS $ENV{UNICORNDIR}
|
|
||||||
PATH_SUFFIXES include)
|
|
||||||
|
|
||||||
find_library(LIBUNICORN_LIBRARY
|
|
||||||
NAMES unicorn
|
|
||||||
HINTS $ENV{UNICORNDIR})
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(unicorn DEFAULT_MSG
|
|
||||||
LIBUNICORN_LIBRARY LIBUNICORN_INCLUDE_DIR)
|
|
||||||
mark_as_advanced(LIBUNICORN_INCLUDE_DIR LIBUNICORN_LIBRARY)
|
|
|
@ -24,6 +24,7 @@ if (MSVC)
|
||||||
# /W3 - Level 3 warnings
|
# /W3 - Level 3 warnings
|
||||||
# /MP - Multi-threaded compilation
|
# /MP - Multi-threaded compilation
|
||||||
# /Zi - Output debugging information
|
# /Zi - Output debugging information
|
||||||
|
# /Zm - Specifies the precompiled header memory allocation limit
|
||||||
# /Zo - Enhanced debug info for optimized builds
|
# /Zo - Enhanced debug info for optimized builds
|
||||||
# /permissive- - Enables stricter C++ standards conformance checks
|
# /permissive- - Enables stricter C++ standards conformance checks
|
||||||
# /EHsc - C++-only exception handling semantics
|
# /EHsc - C++-only exception handling semantics
|
||||||
|
@ -36,6 +37,7 @@ if (MSVC)
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
/MP
|
/MP
|
||||||
/Zi
|
/Zi
|
||||||
|
/Zm200
|
||||||
/Zo
|
/Zo
|
||||||
/permissive-
|
/permissive-
|
||||||
/EHsc
|
/EHsc
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "audio_core/delay_line.h"
|
#include "audio_core/delay_line.h"
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
|
@ -73,6 +73,7 @@ add_library(common STATIC
|
||||||
hex_util.h
|
hex_util.h
|
||||||
host_memory.cpp
|
host_memory.cpp
|
||||||
host_memory.h
|
host_memory.h
|
||||||
|
input.h
|
||||||
intrusive_red_black_tree.h
|
intrusive_red_black_tree.h
|
||||||
literals.h
|
literals.h
|
||||||
logging/backend.cpp
|
logging/backend.cpp
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
@ -44,4 +45,10 @@ template <typename T>
|
||||||
return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
|
return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires std::is_integral_v<T>
|
||||||
|
[[nodiscard]] T NextPow2(T value) {
|
||||||
|
return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U)));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/uuid.h"
|
||||||
|
|
||||||
|
namespace Common::Input {
|
||||||
|
|
||||||
|
// Type of data that is expected to recieve or send
|
||||||
|
enum class InputType {
|
||||||
|
None,
|
||||||
|
Battery,
|
||||||
|
Button,
|
||||||
|
Stick,
|
||||||
|
Analog,
|
||||||
|
Trigger,
|
||||||
|
Motion,
|
||||||
|
Touch,
|
||||||
|
Color,
|
||||||
|
Vibration,
|
||||||
|
Nfc,
|
||||||
|
Ir,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal battery charge level
|
||||||
|
enum class BatteryLevel : u32 {
|
||||||
|
None,
|
||||||
|
Empty,
|
||||||
|
Critical,
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
Full,
|
||||||
|
Charging,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PollingMode {
|
||||||
|
// Constant polling of buttons, analogs and motion data
|
||||||
|
Active,
|
||||||
|
// Only update on button change, digital analogs
|
||||||
|
Pasive,
|
||||||
|
// Enable near field communication polling
|
||||||
|
NFC,
|
||||||
|
// Enable infrared camera polling
|
||||||
|
IR,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vibration reply from the controller
|
||||||
|
enum class VibrationError {
|
||||||
|
None,
|
||||||
|
NotSupported,
|
||||||
|
Disabled,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Polling mode reply from the controller
|
||||||
|
enum class PollingError {
|
||||||
|
None,
|
||||||
|
NotSupported,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hint for amplification curve to be used
|
||||||
|
enum class VibrationAmplificationType {
|
||||||
|
Linear,
|
||||||
|
Exponential,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Analog properties for calibration
|
||||||
|
struct AnalogProperties {
|
||||||
|
// Anything below this value will be detected as zero
|
||||||
|
float deadzone{};
|
||||||
|
// Anyting above this values will be detected as one
|
||||||
|
float range{1.0f};
|
||||||
|
// Minimum value to be detected as active
|
||||||
|
float threshold{0.5f};
|
||||||
|
// Drift correction applied to the raw data
|
||||||
|
float offset{};
|
||||||
|
// Invert direction of the sensor data
|
||||||
|
bool inverted{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single analog sensor data
|
||||||
|
struct AnalogStatus {
|
||||||
|
float value{};
|
||||||
|
float raw_value{};
|
||||||
|
AnalogProperties properties{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Button data
|
||||||
|
struct ButtonStatus {
|
||||||
|
Common::UUID uuid{};
|
||||||
|
bool value{};
|
||||||
|
bool inverted{};
|
||||||
|
bool toggle{};
|
||||||
|
bool locked{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal battery data
|
||||||
|
using BatteryStatus = BatteryLevel;
|
||||||
|
|
||||||
|
// Analog and digital joystick data
|
||||||
|
struct StickStatus {
|
||||||
|
Common::UUID uuid{};
|
||||||
|
AnalogStatus x{};
|
||||||
|
AnalogStatus y{};
|
||||||
|
bool left{};
|
||||||
|
bool right{};
|
||||||
|
bool up{};
|
||||||
|
bool down{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Analog and digital trigger data
|
||||||
|
struct TriggerStatus {
|
||||||
|
Common::UUID uuid{};
|
||||||
|
AnalogStatus analog{};
|
||||||
|
ButtonStatus pressed{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3D vector representing motion input
|
||||||
|
struct MotionSensor {
|
||||||
|
AnalogStatus x{};
|
||||||
|
AnalogStatus y{};
|
||||||
|
AnalogStatus z{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Motion data used to calculate controller orientation
|
||||||
|
struct MotionStatus {
|
||||||
|
// Gyroscope vector measurement in radians/s.
|
||||||
|
MotionSensor gyro{};
|
||||||
|
// Acceleration vector measurement in G force
|
||||||
|
MotionSensor accel{};
|
||||||
|
// Time since last measurement in microseconds
|
||||||
|
u64 delta_timestamp{};
|
||||||
|
// Request to update after reading the value
|
||||||
|
bool force_update{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Data of a single point on a touch screen
|
||||||
|
struct TouchStatus {
|
||||||
|
ButtonStatus pressed{};
|
||||||
|
AnalogStatus x{};
|
||||||
|
AnalogStatus y{};
|
||||||
|
int id{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Physical controller color in RGB format
|
||||||
|
struct BodyColorStatus {
|
||||||
|
u32 body{};
|
||||||
|
u32 buttons{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// HD rumble data
|
||||||
|
struct VibrationStatus {
|
||||||
|
f32 low_amplitude{};
|
||||||
|
f32 low_frequency{};
|
||||||
|
f32 high_amplitude{};
|
||||||
|
f32 high_frequency{};
|
||||||
|
VibrationAmplificationType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Physical controller LED pattern
|
||||||
|
struct LedStatus {
|
||||||
|
bool led_1{};
|
||||||
|
bool led_2{};
|
||||||
|
bool led_3{};
|
||||||
|
bool led_4{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// List of buttons to be passed to Qt that can be translated
|
||||||
|
enum class ButtonNames {
|
||||||
|
Undefined,
|
||||||
|
Invalid,
|
||||||
|
// This will display the engine name instead of the button name
|
||||||
|
Engine,
|
||||||
|
// This will display the button by value instead of the button name
|
||||||
|
Value,
|
||||||
|
ButtonLeft,
|
||||||
|
ButtonRight,
|
||||||
|
ButtonDown,
|
||||||
|
ButtonUp,
|
||||||
|
TriggerZ,
|
||||||
|
TriggerR,
|
||||||
|
TriggerL,
|
||||||
|
ButtonA,
|
||||||
|
ButtonB,
|
||||||
|
ButtonX,
|
||||||
|
ButtonY,
|
||||||
|
ButtonStart,
|
||||||
|
|
||||||
|
// DS4 button names
|
||||||
|
L1,
|
||||||
|
L2,
|
||||||
|
L3,
|
||||||
|
R1,
|
||||||
|
R2,
|
||||||
|
R3,
|
||||||
|
Circle,
|
||||||
|
Cross,
|
||||||
|
Square,
|
||||||
|
Triangle,
|
||||||
|
Share,
|
||||||
|
Options,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback data consisting of an input type and the equivalent data status
|
||||||
|
struct CallbackStatus {
|
||||||
|
InputType type{InputType::None};
|
||||||
|
ButtonStatus button_status{};
|
||||||
|
StickStatus stick_status{};
|
||||||
|
AnalogStatus analog_status{};
|
||||||
|
TriggerStatus trigger_status{};
|
||||||
|
MotionStatus motion_status{};
|
||||||
|
TouchStatus touch_status{};
|
||||||
|
BodyColorStatus color_status{};
|
||||||
|
BatteryStatus battery_status{};
|
||||||
|
VibrationStatus vibration_status{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Triggered once every input change
|
||||||
|
struct InputCallback {
|
||||||
|
std::function<void(const CallbackStatus&)> on_change;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An abstract class template for an input device (a button, an analog input, etc.).
|
||||||
|
class InputDevice {
|
||||||
|
public:
|
||||||
|
virtual ~InputDevice() = default;
|
||||||
|
|
||||||
|
// Request input device to update if necessary
|
||||||
|
virtual void SoftUpdate() {}
|
||||||
|
|
||||||
|
// Force input device to update data regardless of the current state
|
||||||
|
virtual void ForceUpdate() {}
|
||||||
|
|
||||||
|
// Sets the function to be triggered when input changes
|
||||||
|
void SetCallback(InputCallback callback_) {
|
||||||
|
callback = std::move(callback_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triggers the function set in the callback
|
||||||
|
void TriggerOnChange(const CallbackStatus& status) {
|
||||||
|
if (callback.on_change) {
|
||||||
|
callback.on_change(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
InputCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An abstract class template for an output device (rumble, LED pattern, polling mode).
|
||||||
|
class OutputDevice {
|
||||||
|
public:
|
||||||
|
virtual ~OutputDevice() = default;
|
||||||
|
|
||||||
|
virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {}
|
||||||
|
|
||||||
|
virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
|
||||||
|
return VibrationError::NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||||
|
return PollingError::NotSupported;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An abstract class template for a factory that can create input devices.
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
class Factory {
|
||||||
|
public:
|
||||||
|
virtual ~Factory() = default;
|
||||||
|
virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Impl {
|
||||||
|
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
|
||||||
|
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
struct FactoryList {
|
||||||
|
static FactoryListType<InputDeviceType> list;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
|
||||||
|
|
||||||
|
} // namespace Impl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an input device factory.
|
||||||
|
* @tparam InputDeviceType the type of input devices the factory can create
|
||||||
|
* @param name the name of the factory. Will be used to match the "engine" parameter when creating
|
||||||
|
* a device
|
||||||
|
* @param factory the factory object to register
|
||||||
|
*/
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
|
||||||
|
auto pair = std::make_pair(name, std::move(factory));
|
||||||
|
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
|
||||||
|
LOG_ERROR(Input, "Factory '{}' already registered", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters an input device factory.
|
||||||
|
* @tparam InputDeviceType the type of input devices the factory can create
|
||||||
|
* @param name the name of the factory to unregister
|
||||||
|
*/
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
void UnregisterFactory(const std::string& name) {
|
||||||
|
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
|
||||||
|
LOG_ERROR(Input, "Factory '{}' not registered", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an input device from given paramters.
|
||||||
|
* @tparam InputDeviceType the type of input devices to create
|
||||||
|
* @param params a serialized ParamPackage string that contains all parameters for creating the
|
||||||
|
* device
|
||||||
|
*/
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
std::unique_ptr<InputDeviceType> CreateDeviceFromString(const std::string& params) {
|
||||||
|
const Common::ParamPackage package(params);
|
||||||
|
const std::string engine = package.Get("engine", "null");
|
||||||
|
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
|
||||||
|
const auto pair = factory_list.find(engine);
|
||||||
|
if (pair == factory_list.end()) {
|
||||||
|
if (engine != "null") {
|
||||||
|
LOG_ERROR(Input, "Unknown engine name: {}", engine);
|
||||||
|
}
|
||||||
|
return std::make_unique<InputDeviceType>();
|
||||||
|
}
|
||||||
|
return pair->second->Create(package);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an input device from given paramters.
|
||||||
|
* @tparam InputDeviceType the type of input devices to create
|
||||||
|
* @param A ParamPackage that contains all parameters for creating the device
|
||||||
|
*/
|
||||||
|
template <typename InputDeviceType>
|
||||||
|
std::unique_ptr<InputDeviceType> CreateDevice(const Common::ParamPackage package) {
|
||||||
|
const std::string engine = package.Get("engine", "null");
|
||||||
|
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
|
||||||
|
const auto pair = factory_list.find(engine);
|
||||||
|
if (pair == factory_list.end()) {
|
||||||
|
if (engine != "null") {
|
||||||
|
LOG_ERROR(Input, "Unknown engine name: {}", engine);
|
||||||
|
}
|
||||||
|
return std::make_unique<InputDeviceType>();
|
||||||
|
}
|
||||||
|
return pair->second->Create(package);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::Input
|
|
@ -114,6 +114,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||||
SUB(Service, NGCT) \
|
SUB(Service, NGCT) \
|
||||||
SUB(Service, NIFM) \
|
SUB(Service, NIFM) \
|
||||||
SUB(Service, NIM) \
|
SUB(Service, NIM) \
|
||||||
|
SUB(Service, NOTIF) \
|
||||||
SUB(Service, NPNS) \
|
SUB(Service, NPNS) \
|
||||||
SUB(Service, NS) \
|
SUB(Service, NS) \
|
||||||
SUB(Service, NVDRV) \
|
SUB(Service, NVDRV) \
|
||||||
|
|
|
@ -82,6 +82,7 @@ enum class Class : u8 {
|
||||||
Service_NGCT, ///< The NGCT (No Good Content for Terra) service
|
Service_NGCT, ///< The NGCT (No Good Content for Terra) service
|
||||||
Service_NIFM, ///< The NIFM (Network interface) service
|
Service_NIFM, ///< The NIFM (Network interface) service
|
||||||
Service_NIM, ///< The NIM service
|
Service_NIM, ///< The NIM service
|
||||||
|
Service_NOTIF, ///< The NOTIF (Notification) service
|
||||||
Service_NPNS, ///< The NPNS service
|
Service_NPNS, ///< The NPNS service
|
||||||
Service_NS, ///< The NS services
|
Service_NS, ///< The NS services
|
||||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||||
|
|
|
@ -183,6 +183,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||||
values.max_anisotropy.SetGlobal(true);
|
values.max_anisotropy.SetGlobal(true);
|
||||||
values.use_speed_limit.SetGlobal(true);
|
values.use_speed_limit.SetGlobal(true);
|
||||||
values.speed_limit.SetGlobal(true);
|
values.speed_limit.SetGlobal(true);
|
||||||
|
values.fps_cap.SetGlobal(true);
|
||||||
values.use_disk_shader_cache.SetGlobal(true);
|
values.use_disk_shader_cache.SetGlobal(true);
|
||||||
values.gpu_accuracy.SetGlobal(true);
|
values.gpu_accuracy.SetGlobal(true);
|
||||||
values.use_asynchronous_gpu_emulation.SetGlobal(true);
|
values.use_asynchronous_gpu_emulation.SetGlobal(true);
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -525,7 +524,7 @@ struct Values {
|
||||||
Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
|
Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
|
||||||
Setting<bool> accelerate_astc{true, "accelerate_astc"};
|
Setting<bool> accelerate_astc{true, "accelerate_astc"};
|
||||||
Setting<bool> use_vsync{true, "use_vsync"};
|
Setting<bool> use_vsync{true, "use_vsync"};
|
||||||
BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
|
RangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
|
||||||
BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
|
BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
|
||||||
RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
|
RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
|
||||||
ShaderBackend::SPIRV, "shader_backend"};
|
ShaderBackend::SPIRV, "shader_backend"};
|
||||||
|
@ -560,25 +559,19 @@ struct Values {
|
||||||
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
|
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
|
||||||
|
|
||||||
Setting<bool> motion_enabled{true, "motion_enabled"};
|
Setting<bool> motion_enabled{true, "motion_enabled"};
|
||||||
BasicSetting<std::string> motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01",
|
|
||||||
"motion_device"};
|
|
||||||
BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
|
BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
|
||||||
|
BasicSetting<bool> enable_udp_controller{false, "enable_udp_controller"};
|
||||||
|
|
||||||
BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
|
BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
|
||||||
BasicSetting<bool> tas_enable{false, "tas_enable"};
|
BasicSetting<bool> tas_enable{false, "tas_enable"};
|
||||||
BasicSetting<bool> tas_loop{false, "tas_loop"};
|
BasicSetting<bool> tas_loop{false, "tas_loop"};
|
||||||
BasicSetting<bool> tas_swap_controllers{true, "tas_swap_controllers"};
|
|
||||||
|
|
||||||
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
|
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
|
||||||
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
|
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
|
||||||
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
|
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||||
std::string mouse_device;
|
|
||||||
MouseButtonsRaw mouse_buttons;
|
|
||||||
|
|
||||||
BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
|
BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
|
||||||
BasicSetting<bool> keyboard_enabled{false, "keyboard_enabled"};
|
BasicSetting<bool> keyboard_enabled{false, "keyboard_enabled"};
|
||||||
KeyboardKeysRaw keyboard_keys;
|
|
||||||
KeyboardModsRaw keyboard_mods;
|
|
||||||
|
|
||||||
BasicSetting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
|
BasicSetting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
|
||||||
ButtonsRaw debug_pad_buttons;
|
ButtonsRaw debug_pad_buttons;
|
||||||
|
@ -586,14 +579,11 @@ struct Values {
|
||||||
|
|
||||||
TouchscreenInput touchscreen;
|
TouchscreenInput touchscreen;
|
||||||
|
|
||||||
BasicSetting<bool> use_touch_from_button{false, "use_touch_from_button"};
|
|
||||||
BasicSetting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850",
|
BasicSetting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850",
|
||||||
"touch_device"};
|
"touch_device"};
|
||||||
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
|
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
|
||||||
std::vector<TouchFromButtonMap> touch_from_button_maps;
|
std::vector<TouchFromButtonMap> touch_from_button_maps;
|
||||||
|
|
||||||
std::atomic_bool is_device_reload_pending{true};
|
|
||||||
|
|
||||||
// Data Storage
|
// Data Storage
|
||||||
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
|
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
|
||||||
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
|
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
|
||||||
|
@ -614,6 +604,7 @@ struct Values {
|
||||||
BasicSetting<bool> extended_logging{false, "extended_logging"};
|
BasicSetting<bool> extended_logging{false, "extended_logging"};
|
||||||
BasicSetting<bool> use_debug_asserts{false, "use_debug_asserts"};
|
BasicSetting<bool> use_debug_asserts{false, "use_debug_asserts"};
|
||||||
BasicSetting<bool> use_auto_stub{false, "use_auto_stub"};
|
BasicSetting<bool> use_auto_stub{false, "use_auto_stub"};
|
||||||
|
BasicSetting<bool> enable_all_controllers{false, "enable_all_controllers"};
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
|
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
|
||||||
|
|
|
@ -62,11 +62,22 @@ enum Values : int {
|
||||||
|
|
||||||
constexpr int STICK_HID_BEGIN = LStick;
|
constexpr int STICK_HID_BEGIN = LStick;
|
||||||
constexpr int STICK_HID_END = NumAnalogs;
|
constexpr int STICK_HID_END = NumAnalogs;
|
||||||
constexpr int NUM_STICKS_HID = NumAnalogs;
|
|
||||||
|
|
||||||
extern const std::array<const char*, NumAnalogs> mapping;
|
extern const std::array<const char*, NumAnalogs> mapping;
|
||||||
} // namespace NativeAnalog
|
} // namespace NativeAnalog
|
||||||
|
|
||||||
|
namespace NativeTrigger {
|
||||||
|
enum Values : int {
|
||||||
|
LTrigger,
|
||||||
|
RTrigger,
|
||||||
|
|
||||||
|
NumTriggers,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int TRIGGER_HID_BEGIN = LTrigger;
|
||||||
|
constexpr int TRIGGER_HID_END = NumTriggers;
|
||||||
|
} // namespace NativeTrigger
|
||||||
|
|
||||||
namespace NativeVibration {
|
namespace NativeVibration {
|
||||||
enum Values : int {
|
enum Values : int {
|
||||||
LeftVibrationDevice,
|
LeftVibrationDevice,
|
||||||
|
@ -115,10 +126,20 @@ constexpr int NUM_MOUSE_HID = NumMouseButtons;
|
||||||
extern const std::array<const char*, NumMouseButtons> mapping;
|
extern const std::array<const char*, NumMouseButtons> mapping;
|
||||||
} // namespace NativeMouseButton
|
} // namespace NativeMouseButton
|
||||||
|
|
||||||
|
namespace NativeMouseWheel {
|
||||||
|
enum Values {
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
|
||||||
|
NumMouseWheels,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const std::array<const char*, NumMouseWheels> mapping;
|
||||||
|
} // namespace NativeMouseWheel
|
||||||
|
|
||||||
namespace NativeKeyboard {
|
namespace NativeKeyboard {
|
||||||
enum Keys {
|
enum Keys {
|
||||||
None,
|
None,
|
||||||
Error,
|
|
||||||
|
|
||||||
A = 4,
|
A = 4,
|
||||||
B,
|
B,
|
||||||
|
@ -156,22 +177,22 @@ enum Keys {
|
||||||
N8,
|
N8,
|
||||||
N9,
|
N9,
|
||||||
N0,
|
N0,
|
||||||
Enter,
|
Return,
|
||||||
Escape,
|
Escape,
|
||||||
Backspace,
|
Backspace,
|
||||||
Tab,
|
Tab,
|
||||||
Space,
|
Space,
|
||||||
Minus,
|
Minus,
|
||||||
Equal,
|
Plus,
|
||||||
LeftBrace,
|
OpenBracket,
|
||||||
RightBrace,
|
CloseBracket,
|
||||||
Backslash,
|
Pipe,
|
||||||
Tilde,
|
Tilde,
|
||||||
Semicolon,
|
Semicolon,
|
||||||
Apostrophe,
|
Quote,
|
||||||
Grave,
|
Backquote,
|
||||||
Comma,
|
Comma,
|
||||||
Dot,
|
Period,
|
||||||
Slash,
|
Slash,
|
||||||
CapsLockKey,
|
CapsLockKey,
|
||||||
|
|
||||||
|
@ -188,7 +209,7 @@ enum Keys {
|
||||||
F11,
|
F11,
|
||||||
F12,
|
F12,
|
||||||
|
|
||||||
SystemRequest,
|
PrintScreen,
|
||||||
ScrollLockKey,
|
ScrollLockKey,
|
||||||
Pause,
|
Pause,
|
||||||
Insert,
|
Insert,
|
||||||
|
@ -257,8 +278,18 @@ enum Keys {
|
||||||
ScrollLockActive,
|
ScrollLockActive,
|
||||||
KPComma,
|
KPComma,
|
||||||
|
|
||||||
KPLeftParenthesis,
|
Ro = 0x87,
|
||||||
KPRightParenthesis,
|
KatakanaHiragana,
|
||||||
|
Yen,
|
||||||
|
Henkan,
|
||||||
|
Muhenkan,
|
||||||
|
NumPadCommaPc98,
|
||||||
|
|
||||||
|
HangulEnglish = 0x90,
|
||||||
|
Hanja,
|
||||||
|
KatakanaKey,
|
||||||
|
HiraganaKey,
|
||||||
|
ZenkakuHankaku,
|
||||||
|
|
||||||
LeftControlKey = 0xE0,
|
LeftControlKey = 0xE0,
|
||||||
LeftShiftKey,
|
LeftShiftKey,
|
||||||
|
@ -307,6 +338,8 @@ enum Modifiers {
|
||||||
CapsLock,
|
CapsLock,
|
||||||
ScrollLock,
|
ScrollLock,
|
||||||
NumLock,
|
NumLock,
|
||||||
|
Katakana,
|
||||||
|
Hiragana,
|
||||||
|
|
||||||
NumKeyboardMods,
|
NumKeyboardMods,
|
||||||
};
|
};
|
||||||
|
@ -324,11 +357,6 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
|
||||||
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
|
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
|
||||||
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
|
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
|
||||||
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
|
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
|
||||||
using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
|
|
||||||
|
|
||||||
using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
|
|
||||||
using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
|
|
||||||
using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
|
|
||||||
|
|
||||||
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
||||||
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
||||||
|
@ -342,6 +370,11 @@ enum class ControllerType {
|
||||||
RightJoycon,
|
RightJoycon,
|
||||||
Handheld,
|
Handheld,
|
||||||
GameCube,
|
GameCube,
|
||||||
|
Pokeball,
|
||||||
|
NES,
|
||||||
|
SNES,
|
||||||
|
N64,
|
||||||
|
SegaGenesis,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlayerInput {
|
struct PlayerInput {
|
||||||
|
@ -349,7 +382,6 @@ struct PlayerInput {
|
||||||
ControllerType controller_type;
|
ControllerType controller_type;
|
||||||
ButtonsRaw buttons;
|
ButtonsRaw buttons;
|
||||||
AnalogsRaw analogs;
|
AnalogsRaw analogs;
|
||||||
VibrationsRaw vibrations;
|
|
||||||
MotionsRaw motions;
|
MotionsRaw motions;
|
||||||
|
|
||||||
bool vibration_enabled;
|
bool vibration_enabled;
|
||||||
|
|
|
@ -71,9 +71,6 @@ static CPUCaps Detect() {
|
||||||
else
|
else
|
||||||
caps.manufacturer = Manufacturer::Unknown;
|
caps.manufacturer = Manufacturer::Unknown;
|
||||||
|
|
||||||
u32 family = {};
|
|
||||||
u32 model = {};
|
|
||||||
|
|
||||||
__cpuid(cpu_id, 0x80000000);
|
__cpuid(cpu_id, 0x80000000);
|
||||||
|
|
||||||
u32 max_ex_fn = cpu_id[0];
|
u32 max_ex_fn = cpu_id[0];
|
||||||
|
@ -84,15 +81,6 @@ static CPUCaps Detect() {
|
||||||
// Detect family and other miscellaneous features
|
// Detect family and other miscellaneous features
|
||||||
if (max_std_fn >= 1) {
|
if (max_std_fn >= 1) {
|
||||||
__cpuid(cpu_id, 0x00000001);
|
__cpuid(cpu_id, 0x00000001);
|
||||||
family = (cpu_id[0] >> 8) & 0xf;
|
|
||||||
model = (cpu_id[0] >> 4) & 0xf;
|
|
||||||
if (family == 0xf) {
|
|
||||||
family += (cpu_id[0] >> 20) & 0xff;
|
|
||||||
}
|
|
||||||
if (family >= 6) {
|
|
||||||
model += ((cpu_id[0] >> 16) & 0xf) << 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((cpu_id[3] >> 25) & 1)
|
if ((cpu_id[3] >> 25) & 1)
|
||||||
caps.sse = true;
|
caps.sse = true;
|
||||||
if ((cpu_id[3] >> 26) & 1)
|
if ((cpu_id[3] >> 26) & 1)
|
||||||
|
|
|
@ -15,26 +15,26 @@
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
u64 EstimateRDTSCFrequency() {
|
u64 EstimateRDTSCFrequency() {
|
||||||
const auto milli_10 = std::chrono::milliseconds{10};
|
// Discard the first result measuring the rdtsc.
|
||||||
// get current time
|
|
||||||
_mm_mfence();
|
_mm_mfence();
|
||||||
const u64 tscStart = __rdtsc();
|
__rdtsc();
|
||||||
const auto startTime = std::chrono::high_resolution_clock::now();
|
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||||
// wait roughly 3 seconds
|
|
||||||
while (true) {
|
|
||||||
auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::high_resolution_clock::now() - startTime);
|
|
||||||
if (milli.count() >= 3000)
|
|
||||||
break;
|
|
||||||
std::this_thread::sleep_for(milli_10);
|
|
||||||
}
|
|
||||||
const auto endTime = std::chrono::high_resolution_clock::now();
|
|
||||||
_mm_mfence();
|
_mm_mfence();
|
||||||
const u64 tscEnd = __rdtsc();
|
__rdtsc();
|
||||||
// calculate difference
|
|
||||||
const u64 timer_diff =
|
// Get the current time.
|
||||||
std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
|
const auto start_time = std::chrono::steady_clock::now();
|
||||||
const u64 tsc_diff = tscEnd - tscStart;
|
_mm_mfence();
|
||||||
|
const u64 tsc_start = __rdtsc();
|
||||||
|
// Wait for 200 milliseconds.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{200});
|
||||||
|
const auto end_time = std::chrono::steady_clock::now();
|
||||||
|
_mm_mfence();
|
||||||
|
const u64 tsc_end = __rdtsc();
|
||||||
|
// Calculate differences.
|
||||||
|
const u64 timer_diff = static_cast<u64>(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||||
|
const u64 tsc_diff = tsc_end - tsc_start;
|
||||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||||
return tsc_freq;
|
return tsc_freq;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,11 +132,23 @@ add_library(core STATIC
|
||||||
frontend/emu_window.h
|
frontend/emu_window.h
|
||||||
frontend/framebuffer_layout.cpp
|
frontend/framebuffer_layout.cpp
|
||||||
frontend/framebuffer_layout.h
|
frontend/framebuffer_layout.h
|
||||||
frontend/input_interpreter.cpp
|
|
||||||
frontend/input_interpreter.h
|
|
||||||
frontend/input.h
|
|
||||||
hardware_interrupt_manager.cpp
|
hardware_interrupt_manager.cpp
|
||||||
hardware_interrupt_manager.h
|
hardware_interrupt_manager.h
|
||||||
|
hid/emulated_console.cpp
|
||||||
|
hid/emulated_console.h
|
||||||
|
hid/emulated_controller.cpp
|
||||||
|
hid/emulated_controller.h
|
||||||
|
hid/emulated_devices.cpp
|
||||||
|
hid/emulated_devices.h
|
||||||
|
hid/hid_core.cpp
|
||||||
|
hid/hid_core.h
|
||||||
|
hid/hid_types.h
|
||||||
|
hid/input_converter.cpp
|
||||||
|
hid/input_converter.h
|
||||||
|
hid/input_interpreter.cpp
|
||||||
|
hid/input_interpreter.h
|
||||||
|
hid/motion_input.cpp
|
||||||
|
hid/motion_input.h
|
||||||
hle/api_version.h
|
hle/api_version.h
|
||||||
hle/ipc.h
|
hle/ipc.h
|
||||||
hle/ipc_helpers.h
|
hle/ipc_helpers.h
|
||||||
|
@ -167,12 +179,15 @@ add_library(core STATIC
|
||||||
hle/kernel/k_client_port.h
|
hle/kernel/k_client_port.h
|
||||||
hle/kernel/k_client_session.cpp
|
hle/kernel/k_client_session.cpp
|
||||||
hle/kernel/k_client_session.h
|
hle/kernel/k_client_session.h
|
||||||
|
hle/kernel/k_code_memory.cpp
|
||||||
|
hle/kernel/k_code_memory.h
|
||||||
hle/kernel/k_condition_variable.cpp
|
hle/kernel/k_condition_variable.cpp
|
||||||
hle/kernel/k_condition_variable.h
|
hle/kernel/k_condition_variable.h
|
||||||
hle/kernel/k_event.cpp
|
hle/kernel/k_event.cpp
|
||||||
hle/kernel/k_event.h
|
hle/kernel/k_event.h
|
||||||
hle/kernel/k_handle_table.cpp
|
hle/kernel/k_handle_table.cpp
|
||||||
hle/kernel/k_handle_table.h
|
hle/kernel/k_handle_table.h
|
||||||
|
hle/kernel/k_light_condition_variable.cpp
|
||||||
hle/kernel/k_light_condition_variable.h
|
hle/kernel/k_light_condition_variable.h
|
||||||
hle/kernel/k_light_lock.cpp
|
hle/kernel/k_light_lock.cpp
|
||||||
hle/kernel/k_light_lock.h
|
hle/kernel/k_light_lock.h
|
||||||
|
@ -225,6 +240,7 @@ add_library(core STATIC
|
||||||
hle/kernel/k_system_control.h
|
hle/kernel/k_system_control.h
|
||||||
hle/kernel/k_thread.cpp
|
hle/kernel/k_thread.cpp
|
||||||
hle/kernel/k_thread.h
|
hle/kernel/k_thread.h
|
||||||
|
hle/kernel/k_thread_queue.cpp
|
||||||
hle/kernel/k_thread_queue.h
|
hle/kernel/k_thread_queue.h
|
||||||
hle/kernel/k_trace.h
|
hle/kernel/k_trace.h
|
||||||
hle/kernel/k_transfer_memory.cpp
|
hle/kernel/k_transfer_memory.cpp
|
||||||
|
@ -396,12 +412,15 @@ add_library(core STATIC
|
||||||
hle/service/glue/glue.h
|
hle/service/glue/glue.h
|
||||||
hle/service/glue/glue_manager.cpp
|
hle/service/glue/glue_manager.cpp
|
||||||
hle/service/glue/glue_manager.h
|
hle/service/glue/glue_manager.h
|
||||||
|
hle/service/glue/notif.cpp
|
||||||
|
hle/service/glue/notif.h
|
||||||
hle/service/grc/grc.cpp
|
hle/service/grc/grc.cpp
|
||||||
hle/service/grc/grc.h
|
hle/service/grc/grc.h
|
||||||
hle/service/hid/hid.cpp
|
hle/service/hid/hid.cpp
|
||||||
hle/service/hid/hid.h
|
hle/service/hid/hid.h
|
||||||
hle/service/hid/irs.cpp
|
hle/service/hid/irs.cpp
|
||||||
hle/service/hid/irs.h
|
hle/service/hid/irs.h
|
||||||
|
hle/service/hid/ring_lifo.h
|
||||||
hle/service/hid/xcd.cpp
|
hle/service/hid/xcd.cpp
|
||||||
hle/service/hid/xcd.h
|
hle/service/hid/xcd.h
|
||||||
hle/service/hid/errors.h
|
hle/service/hid/errors.h
|
||||||
|
@ -466,6 +485,8 @@ add_library(core STATIC
|
||||||
hle/service/ns/language.h
|
hle/service/ns/language.h
|
||||||
hle/service/ns/ns.cpp
|
hle/service/ns/ns.cpp
|
||||||
hle/service/ns/ns.h
|
hle/service/ns/ns.h
|
||||||
|
hle/service/ns/pdm_qry.cpp
|
||||||
|
hle/service/ns/pdm_qry.h
|
||||||
hle/service/ns/pl_u.cpp
|
hle/service/ns/pl_u.cpp
|
||||||
hle/service/ns/pl_u.h
|
hle/service/ns/pl_u.h
|
||||||
hle/service/nvdrv/devices/nvdevice.h
|
hle/service/nvdrv/devices/nvdevice.h
|
||||||
|
|
|
@ -86,6 +86,26 @@ public:
|
||||||
num_instructions, MemoryReadCode(pc));
|
num_instructions, MemoryReadCode(pc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
|
||||||
|
VAddr value) override {
|
||||||
|
switch (op) {
|
||||||
|
case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
|
||||||
|
static constexpr u64 ICACHE_LINE_SIZE = 64;
|
||||||
|
|
||||||
|
const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
|
||||||
|
parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
|
||||||
|
parent.ClearInstructionCache();
|
||||||
|
break;
|
||||||
|
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
|
||||||
|
default:
|
||||||
|
LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
|
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
|
||||||
switch (exception) {
|
switch (exception) {
|
||||||
case Dynarmic::A64::Exception::WaitForInterrupt:
|
case Dynarmic::A64::Exception::WaitForInterrupt:
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "core/file_sys/vfs_concat.h"
|
#include "core/file_sys/vfs_concat.h"
|
||||||
#include "core/file_sys/vfs_real.h"
|
#include "core/file_sys/vfs_real.h"
|
||||||
#include "core/hardware_interrupt_manager.h"
|
#include "core/hardware_interrupt_manager.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_scheduler.h"
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
@ -126,7 +127,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||||
|
|
||||||
struct System::Impl {
|
struct System::Impl {
|
||||||
explicit Impl(System& system)
|
explicit Impl(System& system)
|
||||||
: kernel{system}, fs_controller{system}, memory{system},
|
: kernel{system}, fs_controller{system}, memory{system}, hid_core{},
|
||||||
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
|
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
|
||||||
|
|
||||||
SystemResultStatus Run() {
|
SystemResultStatus Run() {
|
||||||
|
@ -391,6 +392,7 @@ struct System::Impl {
|
||||||
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
|
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
|
||||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||||
Core::Memory::Memory memory;
|
Core::Memory::Memory memory;
|
||||||
|
Core::HID::HIDCore hid_core;
|
||||||
CpuManager cpu_manager;
|
CpuManager cpu_manager;
|
||||||
std::atomic_bool is_powered_on{};
|
std::atomic_bool is_powered_on{};
|
||||||
bool exit_lock = false;
|
bool exit_lock = false;
|
||||||
|
@ -519,12 +521,6 @@ const ARM_Interface& System::CurrentArmInterface() const {
|
||||||
return impl->kernel.CurrentPhysicalCore().ArmInterface();
|
return impl->kernel.CurrentPhysicalCore().ArmInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t System::CurrentCoreIndex() const {
|
|
||||||
std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
|
||||||
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
|
|
||||||
return core;
|
|
||||||
}
|
|
||||||
|
|
||||||
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
|
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
|
||||||
return impl->kernel.CurrentPhysicalCore();
|
return impl->kernel.CurrentPhysicalCore();
|
||||||
}
|
}
|
||||||
|
@ -615,6 +611,14 @@ const Kernel::KernelCore& System::Kernel() const {
|
||||||
return impl->kernel;
|
return impl->kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HID::HIDCore& System::HIDCore() {
|
||||||
|
return impl->hid_core;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HID::HIDCore& System::HIDCore() const {
|
||||||
|
return impl->hid_core;
|
||||||
|
}
|
||||||
|
|
||||||
Timing::CoreTiming& System::CoreTiming() {
|
Timing::CoreTiming& System::CoreTiming() {
|
||||||
return impl->core_timing;
|
return impl->core_timing;
|
||||||
}
|
}
|
||||||
|
@ -825,8 +829,6 @@ void System::ApplySettings() {
|
||||||
if (IsPoweredOn()) {
|
if (IsPoweredOn()) {
|
||||||
Renderer().RefreshBaseSettings();
|
Renderer().RefreshBaseSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
Service::HID::ReloadInputDevices();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -89,6 +89,10 @@ namespace Core::Hardware {
|
||||||
class InterruptManager;
|
class InterruptManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class HIDCore;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
class ARM_Interface;
|
class ARM_Interface;
|
||||||
|
@ -204,9 +208,6 @@ public:
|
||||||
/// Gets an ARM interface to the CPU core that is currently running
|
/// Gets an ARM interface to the CPU core that is currently running
|
||||||
[[nodiscard]] const ARM_Interface& CurrentArmInterface() const;
|
[[nodiscard]] const ARM_Interface& CurrentArmInterface() const;
|
||||||
|
|
||||||
/// Gets the index of the currently running CPU core
|
|
||||||
[[nodiscard]] std::size_t CurrentCoreIndex() const;
|
|
||||||
|
|
||||||
/// Gets the physical core for the CPU core that is currently running
|
/// Gets the physical core for the CPU core that is currently running
|
||||||
[[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
|
[[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
|
||||||
|
|
||||||
|
@ -285,6 +286,12 @@ public:
|
||||||
/// Provides a constant reference to the kernel instance.
|
/// Provides a constant reference to the kernel instance.
|
||||||
[[nodiscard]] const Kernel::KernelCore& Kernel() const;
|
[[nodiscard]] const Kernel::KernelCore& Kernel() const;
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the HID interface.
|
||||||
|
[[nodiscard]] HID::HIDCore& HIDCore();
|
||||||
|
|
||||||
|
/// Gets an immutable reference to the HID interface.
|
||||||
|
[[nodiscard]] const HID::HIDCore& HIDCore() const;
|
||||||
|
|
||||||
/// Provides a reference to the internal PerfStats instance.
|
/// Provides a reference to the internal PerfStats instance.
|
||||||
[[nodiscard]] Core::PerfStats& GetPerfStats();
|
[[nodiscard]] Core::PerfStats& GetPerfStats();
|
||||||
|
|
||||||
|
|
|
@ -117,17 +117,18 @@ void CpuManager::MultiCoreRunGuestLoop() {
|
||||||
physical_core = &kernel.CurrentPhysicalCore();
|
physical_core = &kernel.CurrentPhysicalCore();
|
||||||
}
|
}
|
||||||
system.ExitDynarmicProfile();
|
system.ExitDynarmicProfile();
|
||||||
|
{
|
||||||
|
Kernel::KScopedDisableDispatch dd(kernel);
|
||||||
physical_core->ArmInterface().ClearExclusiveState();
|
physical_core->ArmInterface().ClearExclusiveState();
|
||||||
kernel.CurrentScheduler()->RescheduleCurrentCore();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::MultiCoreRunIdleThread() {
|
void CpuManager::MultiCoreRunIdleThread() {
|
||||||
auto& kernel = system.Kernel();
|
auto& kernel = system.Kernel();
|
||||||
while (true) {
|
while (true) {
|
||||||
auto& physical_core = kernel.CurrentPhysicalCore();
|
Kernel::KScopedDisableDispatch dd(kernel);
|
||||||
physical_core.Idle();
|
kernel.CurrentPhysicalCore().Idle();
|
||||||
kernel.CurrentScheduler()->RescheduleCurrentCore();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,12 +136,12 @@ void CpuManager::MultiCoreRunSuspendThread() {
|
||||||
auto& kernel = system.Kernel();
|
auto& kernel = system.Kernel();
|
||||||
kernel.CurrentScheduler()->OnThreadStart();
|
kernel.CurrentScheduler()->OnThreadStart();
|
||||||
while (true) {
|
while (true) {
|
||||||
auto core = kernel.GetCurrentHostThreadID();
|
auto core = kernel.CurrentPhysicalCoreIndex();
|
||||||
auto& scheduler = *kernel.CurrentScheduler();
|
auto& scheduler = *kernel.CurrentScheduler();
|
||||||
Kernel::KThread* current_thread = scheduler.GetCurrentThread();
|
Kernel::KThread* current_thread = scheduler.GetCurrentThread();
|
||||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
|
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
|
||||||
ASSERT(scheduler.ContextSwitchPending());
|
ASSERT(scheduler.ContextSwitchPending());
|
||||||
ASSERT(core == kernel.GetCurrentHostThreadID());
|
ASSERT(core == kernel.CurrentPhysicalCoreIndex());
|
||||||
scheduler.RescheduleCurrentCore();
|
scheduler.RescheduleCurrentCore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,13 +347,9 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
|
||||||
sc_sync_first_use = false;
|
sc_sync_first_use = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort if emulation was killed before the session really starts
|
// Emulation was stopped
|
||||||
if (!system.IsPoweredOn()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stop_token.stop_requested()) {
|
if (stop_token.stop_requested()) {
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
||||||
|
|
|
@ -5,16 +5,15 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/frontend/applets/controller.h"
|
#include "core/frontend/applets/controller.h"
|
||||||
#include "core/hle/service/hid/controllers/npad.h"
|
#include "core/hid/emulated_controller.h"
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hid/hid_core.h"
|
||||||
#include "core/hle/service/sm/sm.h"
|
#include "core/hid/hid_types.h"
|
||||||
|
|
||||||
namespace Core::Frontend {
|
namespace Core::Frontend {
|
||||||
|
|
||||||
ControllerApplet::~ControllerApplet() = default;
|
ControllerApplet::~ControllerApplet() = default;
|
||||||
|
|
||||||
DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_)
|
DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_) : hid_core{hid_core_} {}
|
||||||
: service_manager{service_manager_} {}
|
|
||||||
|
|
||||||
DefaultControllerApplet::~DefaultControllerApplet() = default;
|
DefaultControllerApplet::~DefaultControllerApplet() = default;
|
||||||
|
|
||||||
|
@ -22,24 +21,20 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
|
||||||
const ControllerParameters& parameters) const {
|
const ControllerParameters& parameters) const {
|
||||||
LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
|
LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
|
||||||
|
|
||||||
auto& npad =
|
|
||||||
service_manager.GetService<Service::HID::Hid>("hid")
|
|
||||||
->GetAppletResource()
|
|
||||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
|
|
||||||
|
|
||||||
auto& players = Settings::values.players.GetValue();
|
|
||||||
|
|
||||||
const std::size_t min_supported_players =
|
const std::size_t min_supported_players =
|
||||||
parameters.enable_single_mode ? 1 : parameters.min_players;
|
parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||||
|
|
||||||
// Disconnect Handheld first.
|
// Disconnect Handheld first.
|
||||||
npad.DisconnectNpadAtIndex(8);
|
auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||||
|
handheld->Disconnect();
|
||||||
|
|
||||||
// Deduce the best configuration based on the input parameters.
|
// Deduce the best configuration based on the input parameters.
|
||||||
for (std::size_t index = 0; index < players.size() - 2; ++index) {
|
for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) {
|
||||||
|
auto* controller = hid_core.GetEmulatedControllerByIndex(index);
|
||||||
|
|
||||||
// First, disconnect all controllers regardless of the value of keep_controllers_connected.
|
// First, disconnect all controllers regardless of the value of keep_controllers_connected.
|
||||||
// This makes it easy to connect the desired controllers.
|
// This makes it easy to connect the desired controllers.
|
||||||
npad.DisconnectNpadAtIndex(index);
|
controller->Disconnect();
|
||||||
|
|
||||||
// Only connect the minimum number of required players.
|
// Only connect the minimum number of required players.
|
||||||
if (index >= min_supported_players) {
|
if (index >= min_supported_players) {
|
||||||
|
@ -49,27 +44,27 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
|
||||||
// Connect controllers based on the following priority list from highest to lowest priority:
|
// Connect controllers based on the following priority list from highest to lowest priority:
|
||||||
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
|
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
|
||||||
if (parameters.allow_pro_controller) {
|
if (parameters.allow_pro_controller) {
|
||||||
npad.AddNewControllerAt(
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
|
||||||
npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
|
controller->Connect();
|
||||||
} else if (parameters.allow_dual_joycons) {
|
} else if (parameters.allow_dual_joycons) {
|
||||||
npad.AddNewControllerAt(
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);
|
||||||
npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
|
controller->Connect();
|
||||||
} else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
|
} else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
|
||||||
// Assign left joycons to even player indices and right joycons to odd player indices.
|
// Assign left joycons to even player indices and right joycons to odd player indices.
|
||||||
// We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
|
// We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
|
||||||
// a right Joycon for Player 2 in 2 Player Assist mode.
|
// a right Joycon for Player 2 in 2 Player Assist mode.
|
||||||
if (index % 2 == 0) {
|
if (index % 2 == 0) {
|
||||||
npad.AddNewControllerAt(
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconLeft);
|
||||||
npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
|
controller->Connect();
|
||||||
} else {
|
} else {
|
||||||
npad.AddNewControllerAt(
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconRight);
|
||||||
npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
|
controller->Connect();
|
||||||
}
|
}
|
||||||
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
|
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
|
||||||
!Settings::values.use_docked_mode.GetValue()) {
|
!Settings::values.use_docked_mode.GetValue()) {
|
||||||
// We should *never* reach here under any normal circumstances.
|
// We should *never* reach here under any normal circumstances.
|
||||||
npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||||
index);
|
controller->Connect();
|
||||||
} else {
|
} else {
|
||||||
UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
|
UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Service::SM {
|
namespace Core::HID {
|
||||||
class ServiceManager;
|
class HIDCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Core::Frontend {
|
namespace Core::Frontend {
|
||||||
|
@ -44,14 +44,14 @@ public:
|
||||||
|
|
||||||
class DefaultControllerApplet final : public ControllerApplet {
|
class DefaultControllerApplet final : public ControllerApplet {
|
||||||
public:
|
public:
|
||||||
explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_);
|
explicit DefaultControllerApplet(HID::HIDCore& hid_core_);
|
||||||
~DefaultControllerApplet() override;
|
~DefaultControllerApplet() override;
|
||||||
|
|
||||||
void ReconfigureControllers(std::function<void()> callback,
|
void ReconfigureControllers(std::function<void()> callback,
|
||||||
const ControllerParameters& parameters) const override;
|
const ControllerParameters& parameters) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Service::SM::ServiceManager& service_manager;
|
HID::HIDCore& hid_core;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core::Frontend
|
} // namespace Core::Frontend
|
||||||
|
|
|
@ -3,66 +3,31 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "common/settings.h"
|
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/input.h"
|
|
||||||
|
|
||||||
namespace Core::Frontend {
|
namespace Core::Frontend {
|
||||||
|
|
||||||
GraphicsContext::~GraphicsContext() = default;
|
GraphicsContext::~GraphicsContext() = default;
|
||||||
|
|
||||||
class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
|
|
||||||
public std::enable_shared_from_this<TouchState> {
|
|
||||||
public:
|
|
||||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
|
|
||||||
return std::make_unique<Device>(shared_from_this());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::mutex mutex;
|
|
||||||
|
|
||||||
Input::TouchStatus status;
|
|
||||||
|
|
||||||
private:
|
|
||||||
class Device : public Input::TouchDevice {
|
|
||||||
public:
|
|
||||||
explicit Device(std::weak_ptr<TouchState>&& touch_state_) : touch_state(touch_state_) {}
|
|
||||||
Input::TouchStatus GetStatus() const override {
|
|
||||||
if (auto state = touch_state.lock()) {
|
|
||||||
std::lock_guard guard{state->mutex};
|
|
||||||
return state->status;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::weak_ptr<TouchState> touch_state;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
EmuWindow::EmuWindow() {
|
EmuWindow::EmuWindow() {
|
||||||
// TODO: Find a better place to set this.
|
// TODO: Find a better place to set this.
|
||||||
config.min_client_area_size =
|
config.min_client_area_size =
|
||||||
std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
|
std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
|
||||||
active_config = config;
|
active_config = config;
|
||||||
touch_state = std::make_shared<TouchState>();
|
|
||||||
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow::~EmuWindow() {
|
EmuWindow::~EmuWindow() {}
|
||||||
Input::UnregisterFactory<Input::TouchDevice>("emu_window");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
std::pair<f32, f32> EmuWindow::MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const {
|
||||||
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
|
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
|
||||||
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
|
const float x =
|
||||||
* @param framebuffer_x Framebuffer x-coordinate to check
|
static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
|
||||||
* @param framebuffer_y Framebuffer y-coordinate to check
|
static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
|
||||||
* @return True if the coordinates are within the touchpad, otherwise false
|
const float y =
|
||||||
*/
|
static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
|
||||||
static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, u32 framebuffer_x,
|
static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
|
||||||
u32 framebuffer_y) {
|
|
||||||
return (framebuffer_y >= layout.screen.top && framebuffer_y < layout.screen.bottom &&
|
return std::make_pair(x, y);
|
||||||
framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
|
std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
|
||||||
|
@ -75,49 +40,6 @@ std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
|
||||||
return std::make_pair(new_x, new_y);
|
return std::make_pair(new_x, new_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow::TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
|
|
||||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (id >= touch_state->status.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard guard{touch_state->mutex};
|
|
||||||
const float x =
|
|
||||||
static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
|
|
||||||
static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
|
|
||||||
const float y =
|
|
||||||
static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
|
|
||||||
static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
|
|
||||||
|
|
||||||
touch_state->status[id] = std::make_tuple(x, y, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuWindow::TouchReleased(size_t id) {
|
|
||||||
if (id >= touch_state->status.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::lock_guard guard{touch_state->mutex};
|
|
||||||
touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuWindow::TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
|
|
||||||
if (id >= touch_state->status.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!std::get<2>(touch_state->status[id])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
|
|
||||||
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
TouchPressed(framebuffer_x, framebuffer_y, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height) {
|
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height) {
|
||||||
NotifyFramebufferLayoutChanged(Layout::DefaultFrameLayout(width, height));
|
NotifyFramebufferLayoutChanged(Layout::DefaultFrameLayout(width, height));
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,28 +112,6 @@ public:
|
||||||
/// Returns if window is shown (not minimized)
|
/// Returns if window is shown (not minimized)
|
||||||
virtual bool IsShown() const = 0;
|
virtual bool IsShown() const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
|
|
||||||
* @param framebuffer_x Framebuffer x-coordinate that was pressed
|
|
||||||
* @param framebuffer_y Framebuffer y-coordinate that was pressed
|
|
||||||
* @param id Touch event ID
|
|
||||||
*/
|
|
||||||
void TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal that a touch released event has occurred (e.g. mouse click released)
|
|
||||||
* @param id Touch event ID
|
|
||||||
*/
|
|
||||||
void TouchReleased(size_t id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window)
|
|
||||||
* @param framebuffer_x Framebuffer x-coordinate
|
|
||||||
* @param framebuffer_y Framebuffer y-coordinate
|
|
||||||
* @param id Touch event ID
|
|
||||||
*/
|
|
||||||
void TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns currently active configuration.
|
* Returns currently active configuration.
|
||||||
* @note Accesses to the returned object need not be consistent because it may be modified in
|
* @note Accesses to the returned object need not be consistent because it may be modified in
|
||||||
|
@ -212,6 +190,11 @@ protected:
|
||||||
client_area_height = size.second;
|
client_area_height = size.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a screen postion into the equivalent touchscreen position.
|
||||||
|
*/
|
||||||
|
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
|
||||||
|
|
||||||
WindowSystemInfo window_info;
|
WindowSystemInfo window_info;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -237,9 +220,6 @@ private:
|
||||||
WindowConfig config; ///< Internal configuration (changes pending for being applied in
|
WindowConfig config; ///< Internal configuration (changes pending for being applied in
|
||||||
/// ProcessConfigurationChanges)
|
/// ProcessConfigurationChanges)
|
||||||
WindowConfig active_config; ///< Internal active configuration
|
WindowConfig active_config; ///< Internal active configuration
|
||||||
|
|
||||||
class TouchState;
|
|
||||||
std::shared_ptr<TouchState> touch_state;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core::Frontend
|
} // namespace Core::Frontend
|
||||||
|
|
|
@ -25,7 +25,12 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
|
||||||
ASSERT(height > 0);
|
ASSERT(height > 0);
|
||||||
// The drawing code needs at least somewhat valid values for both screens
|
// The drawing code needs at least somewhat valid values for both screens
|
||||||
// so just calculate them both even if the other isn't showing.
|
// so just calculate them both even if the other isn't showing.
|
||||||
FramebufferLayout res{width, height, false, {}};
|
FramebufferLayout res{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.screen = {},
|
||||||
|
.is_srgb = false,
|
||||||
|
};
|
||||||
|
|
||||||
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
|
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
|
||||||
const float emulation_aspect_ratio = EmulationAspectRatio(
|
const float emulation_aspect_ratio = EmulationAspectRatio(
|
||||||
|
|
|
@ -35,17 +35,8 @@ enum class AspectRatio {
|
||||||
struct FramebufferLayout {
|
struct FramebufferLayout {
|
||||||
u32 width{ScreenUndocked::Width};
|
u32 width{ScreenUndocked::Width};
|
||||||
u32 height{ScreenUndocked::Height};
|
u32 height{ScreenUndocked::Height};
|
||||||
bool is_srgb{};
|
|
||||||
|
|
||||||
Common::Rectangle<u32> screen;
|
Common::Rectangle<u32> screen;
|
||||||
|
bool is_srgb{};
|
||||||
/**
|
|
||||||
* Returns the ration of pixel size of the screen, compared to the native size of the undocked
|
|
||||||
* Switch screen.
|
|
||||||
*/
|
|
||||||
float GetScalingRatio() const {
|
|
||||||
return static_cast<float>(screen.GetWidth()) / ScreenUndocked::Width;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,217 +0,0 @@
|
||||||
// Copyright 2017 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <tuple>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/param_package.h"
|
|
||||||
#include "common/quaternion.h"
|
|
||||||
#include "common/vector_math.h"
|
|
||||||
|
|
||||||
namespace Input {
|
|
||||||
|
|
||||||
enum class AnalogDirection : u8 {
|
|
||||||
RIGHT,
|
|
||||||
LEFT,
|
|
||||||
UP,
|
|
||||||
DOWN,
|
|
||||||
};
|
|
||||||
struct AnalogProperties {
|
|
||||||
float deadzone;
|
|
||||||
float range;
|
|
||||||
float threshold;
|
|
||||||
};
|
|
||||||
template <typename StatusType>
|
|
||||||
struct InputCallback {
|
|
||||||
std::function<void(StatusType)> on_change;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An abstract class template for an input device (a button, an analog input, etc.).
|
|
||||||
template <typename StatusType>
|
|
||||||
class InputDevice {
|
|
||||||
public:
|
|
||||||
virtual ~InputDevice() = default;
|
|
||||||
virtual StatusType GetStatus() const {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
virtual StatusType GetRawStatus() const {
|
|
||||||
return GetStatus();
|
|
||||||
}
|
|
||||||
virtual AnalogProperties GetAnalogProperties() const {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low,
|
|
||||||
[[maybe_unused]] f32 amp_high,
|
|
||||||
[[maybe_unused]] f32 freq_high) const {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
void SetCallback(InputCallback<StatusType> callback_) {
|
|
||||||
callback = std::move(callback_);
|
|
||||||
}
|
|
||||||
void TriggerOnChange() {
|
|
||||||
if (callback.on_change) {
|
|
||||||
callback.on_change(GetStatus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
InputCallback<StatusType> callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An abstract class template for a factory that can create input devices.
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
class Factory {
|
|
||||||
public:
|
|
||||||
virtual ~Factory() = default;
|
|
||||||
virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace Impl {
|
|
||||||
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
|
|
||||||
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
struct FactoryList {
|
|
||||||
static FactoryListType<InputDeviceType> list;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
|
|
||||||
|
|
||||||
} // namespace Impl
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an input device factory.
|
|
||||||
* @tparam InputDeviceType the type of input devices the factory can create
|
|
||||||
* @param name the name of the factory. Will be used to match the "engine" parameter when creating
|
|
||||||
* a device
|
|
||||||
* @param factory the factory object to register
|
|
||||||
*/
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
|
|
||||||
auto pair = std::make_pair(name, std::move(factory));
|
|
||||||
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
|
|
||||||
LOG_ERROR(Input, "Factory '{}' already registered", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters an input device factory.
|
|
||||||
* @tparam InputDeviceType the type of input devices the factory can create
|
|
||||||
* @param name the name of the factory to unregister
|
|
||||||
*/
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
void UnregisterFactory(const std::string& name) {
|
|
||||||
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
|
|
||||||
LOG_ERROR(Input, "Factory '{}' not registered", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an input device from given paramters.
|
|
||||||
* @tparam InputDeviceType the type of input devices to create
|
|
||||||
* @param params a serialized ParamPackage string contains all parameters for creating the device
|
|
||||||
*/
|
|
||||||
template <typename InputDeviceType>
|
|
||||||
std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
|
|
||||||
const Common::ParamPackage package(params);
|
|
||||||
const std::string engine = package.Get("engine", "null");
|
|
||||||
const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
|
|
||||||
const auto pair = factory_list.find(engine);
|
|
||||||
if (pair == factory_list.end()) {
|
|
||||||
if (engine != "null") {
|
|
||||||
LOG_ERROR(Input, "Unknown engine name: {}", engine);
|
|
||||||
}
|
|
||||||
return std::make_unique<InputDeviceType>();
|
|
||||||
}
|
|
||||||
return pair->second->Create(package);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A button device is an input device that returns bool as status.
|
|
||||||
* true for pressed; false for released.
|
|
||||||
*/
|
|
||||||
using ButtonDevice = InputDevice<bool>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An analog device is an input device that returns a tuple of x and y coordinates as status. The
|
|
||||||
* coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
|
|
||||||
* direction
|
|
||||||
*/
|
|
||||||
using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A vibration device is an input device that returns an unsigned byte as status.
|
|
||||||
* It represents whether the vibration device supports vibration or not.
|
|
||||||
* If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
|
|
||||||
*/
|
|
||||||
using VibrationDevice = InputDevice<u8>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A motion status is an object that returns a tuple of accelerometer state vector,
|
|
||||||
* gyroscope state vector, rotation state vector, orientation state matrix and quaterion state
|
|
||||||
* vector.
|
|
||||||
*
|
|
||||||
* For both 3D vectors:
|
|
||||||
* x+ is the same direction as RIGHT on D-pad.
|
|
||||||
* y+ is normal to the touch screen, pointing outward.
|
|
||||||
* z+ is the same direction as UP on D-pad.
|
|
||||||
*
|
|
||||||
* For accelerometer state vector
|
|
||||||
* Units: g (gravitational acceleration)
|
|
||||||
*
|
|
||||||
* For gyroscope state vector:
|
|
||||||
* Orientation is determined by right-hand rule.
|
|
||||||
* Units: deg/sec
|
|
||||||
*
|
|
||||||
* For rotation state vector
|
|
||||||
* Units: rotations
|
|
||||||
*
|
|
||||||
* For orientation state matrix
|
|
||||||
* x vector
|
|
||||||
* y vector
|
|
||||||
* z vector
|
|
||||||
*
|
|
||||||
* For quaternion state vector
|
|
||||||
* xyz vector
|
|
||||||
* w float
|
|
||||||
*/
|
|
||||||
using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
|
|
||||||
std::array<Common::Vec3f, 3>, Common::Quaternion<f32>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A motion device is an input device that returns a motion status object
|
|
||||||
*/
|
|
||||||
using MotionDevice = InputDevice<MotionStatus>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A touch status is an object that returns an array of 16 tuple elements of two floats and a bool.
|
|
||||||
* The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is
|
|
||||||
* pressed.
|
|
||||||
*/
|
|
||||||
using TouchStatus = std::array<std::tuple<float, float, bool>, 16>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A touch device is an input device that returns a touch status object
|
|
||||||
*/
|
|
||||||
using TouchDevice = InputDevice<TouchStatus>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mouse device is an input device that returns a tuple of two floats and four ints.
|
|
||||||
* The first two floats are X and Y device coordinates of the mouse (from 0-1).
|
|
||||||
* The s32s are the mouse wheel.
|
|
||||||
*/
|
|
||||||
using MouseDevice = InputDevice<std::tuple<float, float, s32, s32>>;
|
|
||||||
|
|
||||||
} // namespace Input
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "core/hid/emulated_console.h"
|
||||||
|
#include "core/hid/input_converter.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
EmulatedConsole::EmulatedConsole() = default;
|
||||||
|
|
||||||
|
EmulatedConsole::~EmulatedConsole() = default;
|
||||||
|
|
||||||
|
void EmulatedConsole::ReloadFromSettings() {
|
||||||
|
// Using first motion device from player 1. No need to assign any unique config at the moment
|
||||||
|
const auto& player = Settings::values.players.GetValue()[0];
|
||||||
|
motion_params = Common::ParamPackage(player.motions[0]);
|
||||||
|
|
||||||
|
ReloadInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::SetTouchParams() {
|
||||||
|
// TODO(german77): Support any number of fingers
|
||||||
|
std::size_t index = 0;
|
||||||
|
|
||||||
|
// Hardcode mouse, touchscreen and cemuhook parameters
|
||||||
|
if (!Settings::values.mouse_enabled) {
|
||||||
|
// We can't use mouse as touch if native mouse is enabled
|
||||||
|
touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
|
||||||
|
}
|
||||||
|
touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
|
||||||
|
touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
|
||||||
|
touch_params[index++] =
|
||||||
|
Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
|
||||||
|
touch_params[index++] =
|
||||||
|
Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
|
||||||
|
|
||||||
|
const auto button_index =
|
||||||
|
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
|
||||||
|
const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
|
||||||
|
|
||||||
|
// Map the rest of the fingers from touch from button configuration
|
||||||
|
for (const auto& config_entry : touch_buttons) {
|
||||||
|
if (index >= touch_params.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Common::ParamPackage params{config_entry};
|
||||||
|
Common::ParamPackage touch_button_params;
|
||||||
|
const int x = params.Get("x", 0);
|
||||||
|
const int y = params.Get("y", 0);
|
||||||
|
params.Erase("x");
|
||||||
|
params.Erase("y");
|
||||||
|
touch_button_params.Set("engine", "touch_from_button");
|
||||||
|
touch_button_params.Set("button", params.Serialize());
|
||||||
|
touch_button_params.Set("x", x);
|
||||||
|
touch_button_params.Set("y", y);
|
||||||
|
touch_button_params.Set("touch_id", static_cast<int>(index));
|
||||||
|
touch_params[index] = touch_button_params;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::ReloadInput() {
|
||||||
|
// If you load any device here add the equivalent to the UnloadInput() function
|
||||||
|
SetTouchParams();
|
||||||
|
|
||||||
|
motion_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(motion_params);
|
||||||
|
if (motion_devices) {
|
||||||
|
motion_devices->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique index for identifying touch device source
|
||||||
|
std::size_t index = 0;
|
||||||
|
for (auto& touch_device : touch_devices) {
|
||||||
|
touch_device = Common::Input::CreateDevice<Common::Input::InputDevice>(touch_params[index]);
|
||||||
|
if (!touch_device) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
touch_device->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||||
|
SetTouch(callback, index);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::UnloadInput() {
|
||||||
|
motion_devices.reset();
|
||||||
|
for (auto& touch : touch_devices) {
|
||||||
|
touch.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::EnableConfiguration() {
|
||||||
|
is_configuring = true;
|
||||||
|
SaveCurrentConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::DisableConfiguration() {
|
||||||
|
is_configuring = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmulatedConsole::IsConfiguring() const {
|
||||||
|
return is_configuring;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::SaveCurrentConfig() {
|
||||||
|
if (!is_configuring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::RestoreConfig() {
|
||||||
|
if (!is_configuring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReloadFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage EmulatedConsole::GetMotionParam() const {
|
||||||
|
return motion_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
|
||||||
|
motion_params = param;
|
||||||
|
ReloadInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
auto& raw_status = console.motion_values.raw_status;
|
||||||
|
auto& emulated = console.motion_values.emulated;
|
||||||
|
|
||||||
|
raw_status = TransformToMotion(callback);
|
||||||
|
emulated.SetAcceleration(Common::Vec3f{
|
||||||
|
raw_status.accel.x.value,
|
||||||
|
raw_status.accel.y.value,
|
||||||
|
raw_status.accel.z.value,
|
||||||
|
});
|
||||||
|
emulated.SetGyroscope(Common::Vec3f{
|
||||||
|
raw_status.gyro.x.value,
|
||||||
|
raw_status.gyro.y.value,
|
||||||
|
raw_status.gyro.z.value,
|
||||||
|
});
|
||||||
|
emulated.UpdateRotation(raw_status.delta_timestamp);
|
||||||
|
emulated.UpdateOrientation(raw_status.delta_timestamp);
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
TriggerOnChange(ConsoleTriggerType::Motion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& motion = console.motion_state;
|
||||||
|
motion.accel = emulated.GetAcceleration();
|
||||||
|
motion.gyro = emulated.GetGyroscope();
|
||||||
|
motion.rotation = emulated.GetGyroscope();
|
||||||
|
motion.orientation = emulated.GetOrientation();
|
||||||
|
motion.quaternion = emulated.GetQuaternion();
|
||||||
|
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
|
||||||
|
|
||||||
|
TriggerOnChange(ConsoleTriggerType::Motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) {
|
||||||
|
if (index >= console.touch_values.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
|
||||||
|
console.touch_values[index] = TransformToTouch(callback);
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
TriggerOnChange(ConsoleTriggerType::Touch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(german77): Remap touch id in sequential order
|
||||||
|
console.touch_state[index] = {
|
||||||
|
.position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
|
||||||
|
.id = static_cast<u32>(console.touch_values[index].id),
|
||||||
|
.pressed = console.touch_values[index].pressed.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
TriggerOnChange(ConsoleTriggerType::Touch);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
|
||||||
|
return console.motion_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchValues EmulatedConsole::GetTouchValues() const {
|
||||||
|
return console.touch_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleMotion EmulatedConsole::GetMotion() const {
|
||||||
|
return console.motion_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchFingerState EmulatedConsole::GetTouch() const {
|
||||||
|
return console.touch_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
|
||||||
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
const ConsoleUpdateCallback& poller = poller_pair.second;
|
||||||
|
if (poller.on_change) {
|
||||||
|
poller.on_change(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
callback_list.insert_or_assign(last_callback_key, update_callback);
|
||||||
|
return last_callback_key++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedConsole::DeleteCallback(int key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
const auto& iterator = callback_list.find(key);
|
||||||
|
if (iterator == callback_list.end()) {
|
||||||
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback_list.erase(iterator);
|
||||||
|
}
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,190 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/input.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/point.h"
|
||||||
|
#include "common/quaternion.h"
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
#include "core/hid/hid_types.h"
|
||||||
|
#include "core/hid/motion_input.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
struct ConsoleMotionInfo {
|
||||||
|
Common::Input::MotionStatus raw_status{};
|
||||||
|
MotionInput emulated{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>;
|
||||||
|
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>;
|
||||||
|
|
||||||
|
using ConsoleMotionParams = Common::ParamPackage;
|
||||||
|
using TouchParams = std::array<Common::ParamPackage, 16>;
|
||||||
|
|
||||||
|
using ConsoleMotionValues = ConsoleMotionInfo;
|
||||||
|
using TouchValues = std::array<Common::Input::TouchStatus, 16>;
|
||||||
|
|
||||||
|
struct TouchFinger {
|
||||||
|
u64 last_touch{};
|
||||||
|
Common::Point<float> position{};
|
||||||
|
u32 id{};
|
||||||
|
TouchAttribute attribute{};
|
||||||
|
bool pressed{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contains all motion related data that is used on the services
|
||||||
|
struct ConsoleMotion {
|
||||||
|
Common::Vec3f accel{};
|
||||||
|
Common::Vec3f gyro{};
|
||||||
|
Common::Vec3f rotation{};
|
||||||
|
std::array<Common::Vec3f, 3> orientation{};
|
||||||
|
Common::Quaternion<f32> quaternion{};
|
||||||
|
bool is_at_rest{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using TouchFingerState = std::array<TouchFinger, 16>;
|
||||||
|
|
||||||
|
struct ConsoleStatus {
|
||||||
|
// Data from input_common
|
||||||
|
ConsoleMotionValues motion_values{};
|
||||||
|
TouchValues touch_values{};
|
||||||
|
|
||||||
|
// Data for HID services
|
||||||
|
ConsoleMotion motion_state{};
|
||||||
|
TouchFingerState touch_state{};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ConsoleTriggerType {
|
||||||
|
Motion,
|
||||||
|
Touch,
|
||||||
|
All,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConsoleUpdateCallback {
|
||||||
|
std::function<void(ConsoleTriggerType)> on_change;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EmulatedConsole {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Contains all input data within the emulated switch console tablet such as touch and motion
|
||||||
|
*/
|
||||||
|
explicit EmulatedConsole();
|
||||||
|
~EmulatedConsole();
|
||||||
|
|
||||||
|
YUZU_NON_COPYABLE(EmulatedConsole);
|
||||||
|
YUZU_NON_MOVEABLE(EmulatedConsole);
|
||||||
|
|
||||||
|
/// Removes all callbacks created from input devices
|
||||||
|
void UnloadInput();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the emulated console into configuring mode
|
||||||
|
* This prevents the modification of the HID state of the emulated console by input commands
|
||||||
|
*/
|
||||||
|
void EnableConfiguration();
|
||||||
|
|
||||||
|
/// Returns the emulated console into normal mode, allowing the modification of the HID state
|
||||||
|
void DisableConfiguration();
|
||||||
|
|
||||||
|
/// Returns true if the emulated console is in configuring mode
|
||||||
|
bool IsConfiguring() const;
|
||||||
|
|
||||||
|
/// Reload all input devices
|
||||||
|
void ReloadInput();
|
||||||
|
|
||||||
|
/// Overrides current mapped devices with the stored configuration and reloads all input devices
|
||||||
|
void ReloadFromSettings();
|
||||||
|
|
||||||
|
/// Saves the current mapped configuration
|
||||||
|
void SaveCurrentConfig();
|
||||||
|
|
||||||
|
/// Reverts any mapped changes made that weren't saved
|
||||||
|
void RestoreConfig();
|
||||||
|
|
||||||
|
// Returns the current mapped motion device
|
||||||
|
Common::ParamPackage GetMotionParam() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current mapped motion device
|
||||||
|
* @param param ParamPackage with controller data to be mapped
|
||||||
|
*/
|
||||||
|
void SetMotionParam(Common::ParamPackage param);
|
||||||
|
|
||||||
|
/// Returns the latest status of motion input from the console with parameters
|
||||||
|
ConsoleMotionValues GetMotionValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of touch input from the console with parameters
|
||||||
|
TouchValues GetTouchValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of motion input from the console
|
||||||
|
ConsoleMotion GetMotion() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of touch input from the console
|
||||||
|
TouchFingerState GetTouch() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a callback to the list of events
|
||||||
|
* @param update_callback A ConsoleUpdateCallback that will be triggered
|
||||||
|
* @return an unique key corresponding to the callback index in the list
|
||||||
|
*/
|
||||||
|
int SetCallback(ConsoleUpdateCallback update_callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a callback from the list stopping any future events to this object
|
||||||
|
* @param key Key corresponding to the callback index in the list
|
||||||
|
*/
|
||||||
|
void DeleteCallback(int key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Creates and stores the touch params
|
||||||
|
void SetTouchParams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the motion status of the console
|
||||||
|
* @param callback A CallbackStatus containing gyro and accelerometer data
|
||||||
|
*/
|
||||||
|
void SetMotion(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the touch status of the console
|
||||||
|
* @param callback A CallbackStatus containing the touch position
|
||||||
|
* @param index Finger ID to be updated
|
||||||
|
*/
|
||||||
|
void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a callback that something has changed on the console status
|
||||||
|
* @param type Input type of the event to trigger
|
||||||
|
*/
|
||||||
|
void TriggerOnChange(ConsoleTriggerType type);
|
||||||
|
|
||||||
|
bool is_configuring{false};
|
||||||
|
f32 motion_sensitivity{0.01f};
|
||||||
|
|
||||||
|
ConsoleMotionParams motion_params;
|
||||||
|
TouchParams touch_params;
|
||||||
|
|
||||||
|
ConsoleMotionDevices motion_devices;
|
||||||
|
TouchDevices touch_devices;
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
std::unordered_map<int, ConsoleUpdateCallback> callback_list;
|
||||||
|
int last_callback_key = 0;
|
||||||
|
|
||||||
|
// Stores the current status of all console input
|
||||||
|
ConsoleStatus console;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,411 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/input.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/point.h"
|
||||||
|
#include "common/quaternion.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
#include "core/hid/hid_types.h"
|
||||||
|
#include "core/hid/motion_input.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
const std::size_t max_emulated_controllers = 2;
|
||||||
|
struct ControllerMotionInfo {
|
||||||
|
Common::Input::MotionStatus raw_status{};
|
||||||
|
MotionInput emulated{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using ButtonDevices =
|
||||||
|
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>;
|
||||||
|
using StickDevices =
|
||||||
|
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
|
||||||
|
using ControllerMotionDevices =
|
||||||
|
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
|
||||||
|
using TriggerDevices =
|
||||||
|
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
|
||||||
|
using BatteryDevices =
|
||||||
|
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||||
|
using OutputDevices =
|
||||||
|
std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
|
||||||
|
|
||||||
|
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
|
||||||
|
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
|
||||||
|
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
|
||||||
|
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
|
||||||
|
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||||
|
using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||||
|
|
||||||
|
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
|
||||||
|
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
|
||||||
|
using TriggerValues =
|
||||||
|
std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
|
||||||
|
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
|
||||||
|
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
|
||||||
|
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
|
||||||
|
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
|
||||||
|
|
||||||
|
struct AnalogSticks {
|
||||||
|
AnalogStickState left{};
|
||||||
|
AnalogStickState right{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ControllerColors {
|
||||||
|
NpadControllerColor fullkey{};
|
||||||
|
NpadControllerColor left{};
|
||||||
|
NpadControllerColor right{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BatteryLevelState {
|
||||||
|
NpadPowerInfo dual{};
|
||||||
|
NpadPowerInfo left{};
|
||||||
|
NpadPowerInfo right{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ControllerMotion {
|
||||||
|
Common::Vec3f accel{};
|
||||||
|
Common::Vec3f gyro{};
|
||||||
|
Common::Vec3f rotation{};
|
||||||
|
std::array<Common::Vec3f, 3> orientation{};
|
||||||
|
bool is_at_rest{};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EmulatedDeviceIndex : u8 {
|
||||||
|
LeftIndex,
|
||||||
|
RightIndex,
|
||||||
|
DualIndex,
|
||||||
|
AllDevices,
|
||||||
|
};
|
||||||
|
|
||||||
|
using MotionState = std::array<ControllerMotion, 2>;
|
||||||
|
|
||||||
|
struct ControllerStatus {
|
||||||
|
// Data from input_common
|
||||||
|
ButtonValues button_values{};
|
||||||
|
SticksValues stick_values{};
|
||||||
|
ControllerMotionValues motion_values{};
|
||||||
|
TriggerValues trigger_values{};
|
||||||
|
ColorValues color_values{};
|
||||||
|
BatteryValues battery_values{};
|
||||||
|
VibrationValues vibration_values{};
|
||||||
|
|
||||||
|
// Data for HID serices
|
||||||
|
NpadButtonState npad_button_state{};
|
||||||
|
DebugPadButton debug_pad_button_state{};
|
||||||
|
AnalogSticks analog_stick_state{};
|
||||||
|
MotionState motion_state{};
|
||||||
|
NpadGcTriggerState gc_trigger_state{};
|
||||||
|
ControllerColors colors_state{};
|
||||||
|
BatteryLevelState battery_state{};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ControllerTriggerType {
|
||||||
|
Button,
|
||||||
|
Stick,
|
||||||
|
Trigger,
|
||||||
|
Motion,
|
||||||
|
Color,
|
||||||
|
Battery,
|
||||||
|
Vibration,
|
||||||
|
Connected,
|
||||||
|
Disconnected,
|
||||||
|
Type,
|
||||||
|
All,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ControllerUpdateCallback {
|
||||||
|
std::function<void(ControllerTriggerType)> on_change;
|
||||||
|
bool is_npad_service;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EmulatedController {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Contains all input data (buttons, joysticks, vibration, and motion) within this controller.
|
||||||
|
* @param npad_id_type npad id type for this specific controller
|
||||||
|
*/
|
||||||
|
explicit EmulatedController(NpadIdType npad_id_type_);
|
||||||
|
~EmulatedController();
|
||||||
|
|
||||||
|
YUZU_NON_COPYABLE(EmulatedController);
|
||||||
|
YUZU_NON_MOVEABLE(EmulatedController);
|
||||||
|
|
||||||
|
/// Converts the controller type from settings to npad type
|
||||||
|
static NpadStyleIndex MapSettingsTypeToNPad(Settings::ControllerType type);
|
||||||
|
|
||||||
|
/// Converts npad type to the equivalent of controller type from settings
|
||||||
|
static Settings::ControllerType MapNPadToSettingsType(NpadStyleIndex type);
|
||||||
|
|
||||||
|
/// Gets the NpadIdType for this controller
|
||||||
|
NpadIdType GetNpadIdType() const;
|
||||||
|
|
||||||
|
/// Sets the NpadStyleIndex for this controller
|
||||||
|
void SetNpadStyleIndex(NpadStyleIndex npad_type_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the NpadStyleIndex for this controller
|
||||||
|
* @param get_temporary_value If true tmp_npad_type will be returned
|
||||||
|
* @return NpadStyleIndex set on the controller
|
||||||
|
*/
|
||||||
|
NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the supported controller types. Disconnects the controller if current type is not
|
||||||
|
* supported
|
||||||
|
* @param supported_styles bitflag with supported types
|
||||||
|
*/
|
||||||
|
void SetSupportedNpadStyleTag(NpadStyleTag supported_styles);
|
||||||
|
|
||||||
|
/// Sets the connected status to true
|
||||||
|
void Connect();
|
||||||
|
|
||||||
|
/// Sets the connected status to false
|
||||||
|
void Disconnect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the emulated connected
|
||||||
|
* @param get_temporary_value If true tmp_is_connected will be returned
|
||||||
|
* @return true if the controller has the connected status
|
||||||
|
*/
|
||||||
|
bool IsConnected(bool get_temporary_value = false) const;
|
||||||
|
|
||||||
|
/// Returns true if vibration is enabled
|
||||||
|
bool IsVibrationEnabled() const;
|
||||||
|
|
||||||
|
/// Removes all callbacks created from input devices
|
||||||
|
void UnloadInput();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the emulated controller into configuring mode
|
||||||
|
* This prevents the modification of the HID state of the emulated controller by input commands
|
||||||
|
*/
|
||||||
|
void EnableConfiguration();
|
||||||
|
|
||||||
|
/// Returns the emulated controller into normal mode, allowing the modification of the HID state
|
||||||
|
void DisableConfiguration();
|
||||||
|
|
||||||
|
/// Returns true if the emulated controller is in configuring mode
|
||||||
|
bool IsConfiguring() const;
|
||||||
|
|
||||||
|
/// Reload all input devices
|
||||||
|
void ReloadInput();
|
||||||
|
|
||||||
|
/// Overrides current mapped devices with the stored configuration and reloads all input devices
|
||||||
|
void ReloadFromSettings();
|
||||||
|
|
||||||
|
/// Saves the current mapped configuration
|
||||||
|
void SaveCurrentConfig();
|
||||||
|
|
||||||
|
/// Reverts any mapped changes made that weren't saved
|
||||||
|
void RestoreConfig();
|
||||||
|
|
||||||
|
/// Returns a vector of mapped devices from the mapped button and stick parameters
|
||||||
|
std::vector<Common::ParamPackage> GetMappedDevices(EmulatedDeviceIndex device_index) const;
|
||||||
|
|
||||||
|
// Returns the current mapped button device
|
||||||
|
Common::ParamPackage GetButtonParam(std::size_t index) const;
|
||||||
|
|
||||||
|
// Returns the current mapped stick device
|
||||||
|
Common::ParamPackage GetStickParam(std::size_t index) const;
|
||||||
|
|
||||||
|
// Returns the current mapped motion device
|
||||||
|
Common::ParamPackage GetMotionParam(std::size_t index) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current mapped button device
|
||||||
|
* @param param ParamPackage with controller data to be mapped
|
||||||
|
*/
|
||||||
|
void SetButtonParam(std::size_t index, Common::ParamPackage param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current mapped stick device
|
||||||
|
* @param param ParamPackage with controller data to be mapped
|
||||||
|
*/
|
||||||
|
void SetStickParam(std::size_t index, Common::ParamPackage param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current mapped motion device
|
||||||
|
* @param param ParamPackage with controller data to be mapped
|
||||||
|
*/
|
||||||
|
void SetMotionParam(std::size_t index, Common::ParamPackage param);
|
||||||
|
|
||||||
|
/// Returns the latest button status from the controller with parameters
|
||||||
|
ButtonValues GetButtonsValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest analog stick status from the controller with parameters
|
||||||
|
SticksValues GetSticksValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest trigger status from the controller with parameters
|
||||||
|
TriggerValues GetTriggersValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest motion status from the controller with parameters
|
||||||
|
ControllerMotionValues GetMotionValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest color status from the controller with parameters
|
||||||
|
ColorValues GetColorsValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest battery status from the controller with parameters
|
||||||
|
BatteryValues GetBatteryValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input for the npad service
|
||||||
|
NpadButtonState GetNpadButtons() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input for the debug pad service
|
||||||
|
DebugPadButton GetDebugPadButtons() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of stick input from the mouse
|
||||||
|
AnalogSticks GetSticks() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of trigger input from the mouse
|
||||||
|
NpadGcTriggerState GetTriggers() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of motion input from the mouse
|
||||||
|
MotionState GetMotions() const;
|
||||||
|
|
||||||
|
/// Returns the latest color value from the controller
|
||||||
|
ControllerColors GetColors() const;
|
||||||
|
|
||||||
|
/// Returns the latest battery status from the controller
|
||||||
|
BatteryLevelState GetBattery() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a specific vibration to the output device
|
||||||
|
* @return returns true if vibration had no errors
|
||||||
|
*/
|
||||||
|
bool SetVibration(std::size_t device_index, VibrationValue vibration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a small vibration to the output device
|
||||||
|
* @return returns true if SetVibration was successfull
|
||||||
|
*/
|
||||||
|
bool TestVibration(std::size_t device_index);
|
||||||
|
|
||||||
|
/// Returns the led pattern corresponding to this emulated controller
|
||||||
|
LedPattern GetLedPattern() const;
|
||||||
|
|
||||||
|
/// Asks the output device to change the player led pattern
|
||||||
|
void SetLedPattern();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a callback to the list of events
|
||||||
|
* @param update_callback A ConsoleUpdateCallback that will be triggered
|
||||||
|
* @return an unique key corresponding to the callback index in the list
|
||||||
|
*/
|
||||||
|
int SetCallback(ControllerUpdateCallback update_callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a callback from the list stopping any future events to this object
|
||||||
|
* @param key Key corresponding to the callback index in the list
|
||||||
|
*/
|
||||||
|
void DeleteCallback(int key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// creates input devices from params
|
||||||
|
void LoadDevices();
|
||||||
|
|
||||||
|
/// Set the params for TAS devices
|
||||||
|
void LoadTASParams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the current controller type against the supported_style_tag
|
||||||
|
* @return true if the controller is supported
|
||||||
|
*/
|
||||||
|
bool IsControllerSupported() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the button status of the controller
|
||||||
|
* @param callback A CallbackStatus containing the button status
|
||||||
|
* @param index Button ID of the to be updated
|
||||||
|
*/
|
||||||
|
void SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
|
||||||
|
Common::UUID uuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the analog stick status of the controller
|
||||||
|
* @param callback A CallbackStatus containing the analog stick status
|
||||||
|
* @param index stick ID of the to be updated
|
||||||
|
*/
|
||||||
|
void SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
|
||||||
|
Common::UUID uuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the trigger status of the controller
|
||||||
|
* @param callback A CallbackStatus containing the trigger status
|
||||||
|
* @param index trigger ID of the to be updated
|
||||||
|
*/
|
||||||
|
void SetTrigger(const Common::Input::CallbackStatus& callback, std::size_t index,
|
||||||
|
Common::UUID uuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the motion status of the controller
|
||||||
|
* @param callback A CallbackStatus containing gyro and accelerometer data
|
||||||
|
* @param index motion ID of the to be updated
|
||||||
|
*/
|
||||||
|
void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the battery status of the controller
|
||||||
|
* @param callback A CallbackStatus containing the battery status
|
||||||
|
* @param index Button ID of the to be updated
|
||||||
|
*/
|
||||||
|
void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a callback that something has changed on the controller status
|
||||||
|
* @param type Input type of the event to trigger
|
||||||
|
* @param is_service_update indicates if this event should only be sent to HID services
|
||||||
|
*/
|
||||||
|
void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
|
||||||
|
|
||||||
|
NpadIdType npad_id_type;
|
||||||
|
NpadStyleIndex npad_type{NpadStyleIndex::None};
|
||||||
|
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||||
|
bool is_connected{false};
|
||||||
|
bool is_configuring{false};
|
||||||
|
f32 motion_sensitivity{0.01f};
|
||||||
|
bool force_update_motion{false};
|
||||||
|
|
||||||
|
// Temporary values to avoid doing changes while the controller is in configuring mode
|
||||||
|
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
|
||||||
|
bool tmp_is_connected{false};
|
||||||
|
|
||||||
|
ButtonParams button_params;
|
||||||
|
StickParams stick_params;
|
||||||
|
ControllerMotionParams motion_params;
|
||||||
|
TriggerParams trigger_params;
|
||||||
|
BatteryParams battery_params;
|
||||||
|
OutputParams output_params;
|
||||||
|
|
||||||
|
ButtonDevices button_devices;
|
||||||
|
StickDevices stick_devices;
|
||||||
|
ControllerMotionDevices motion_devices;
|
||||||
|
TriggerDevices trigger_devices;
|
||||||
|
BatteryDevices battery_devices;
|
||||||
|
OutputDevices output_devices;
|
||||||
|
|
||||||
|
// TAS related variables
|
||||||
|
ButtonParams tas_button_params;
|
||||||
|
StickParams tas_stick_params;
|
||||||
|
ButtonDevices tas_button_devices;
|
||||||
|
StickDevices tas_stick_devices;
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
std::unordered_map<int, ControllerUpdateCallback> callback_list;
|
||||||
|
int last_callback_key = 0;
|
||||||
|
|
||||||
|
// Stores the current status of all controller input
|
||||||
|
ControllerStatus controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,459 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "core/hid/emulated_devices.h"
|
||||||
|
#include "core/hid/input_converter.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
EmulatedDevices::EmulatedDevices() = default;
|
||||||
|
|
||||||
|
EmulatedDevices::~EmulatedDevices() = default;
|
||||||
|
|
||||||
|
void EmulatedDevices::ReloadFromSettings() {
|
||||||
|
ReloadInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::ReloadInput() {
|
||||||
|
// If you load any device here add the equivalent to the UnloadInput() function
|
||||||
|
std::size_t key_index = 0;
|
||||||
|
for (auto& mouse_device : mouse_button_devices) {
|
||||||
|
Common::ParamPackage mouse_params;
|
||||||
|
mouse_params.Set("engine", "mouse");
|
||||||
|
mouse_params.Set("button", static_cast<int>(key_index));
|
||||||
|
mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
|
||||||
|
key_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse_stick_device = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||||
|
"engine:mouse,axis_x:0,axis_y:1");
|
||||||
|
|
||||||
|
// First two axis are reserved for mouse position
|
||||||
|
key_index = 2;
|
||||||
|
for (auto& mouse_device : mouse_analog_devices) {
|
||||||
|
Common::ParamPackage mouse_params;
|
||||||
|
mouse_params.Set("engine", "mouse");
|
||||||
|
mouse_params.Set("axis", static_cast<int>(key_index));
|
||||||
|
mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
|
||||||
|
key_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_index = 0;
|
||||||
|
for (auto& keyboard_device : keyboard_devices) {
|
||||||
|
// Keyboard keys are only mapped on port 1, pad 0
|
||||||
|
Common::ParamPackage keyboard_params;
|
||||||
|
keyboard_params.Set("engine", "keyboard");
|
||||||
|
keyboard_params.Set("button", static_cast<int>(key_index));
|
||||||
|
keyboard_params.Set("port", 1);
|
||||||
|
keyboard_params.Set("pad", 0);
|
||||||
|
keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
|
||||||
|
key_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_index = 0;
|
||||||
|
for (auto& keyboard_device : keyboard_modifier_devices) {
|
||||||
|
// Keyboard moddifiers are only mapped on port 1, pad 1
|
||||||
|
Common::ParamPackage keyboard_params;
|
||||||
|
keyboard_params.Set("engine", "keyboard");
|
||||||
|
keyboard_params.Set("button", static_cast<int>(key_index));
|
||||||
|
keyboard_params.Set("port", 1);
|
||||||
|
keyboard_params.Set("pad", 1);
|
||||||
|
keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
|
||||||
|
key_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
|
||||||
|
if (!mouse_button_devices[index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mouse_button_devices[index]->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||||
|
SetMouseButton(callback, index);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) {
|
||||||
|
if (!mouse_analog_devices[index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mouse_analog_devices[index]->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||||
|
SetMouseAnalog(callback, index);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouse_stick_device) {
|
||||||
|
mouse_stick_device->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback) { SetMouseStick(callback); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
|
||||||
|
if (!keyboard_devices[index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
keyboard_devices[index]->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||||
|
SetKeyboardButton(callback, index);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
|
||||||
|
if (!keyboard_modifier_devices[index]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
keyboard_modifier_devices[index]->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||||
|
SetKeyboardModifier(callback, index);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::UnloadInput() {
|
||||||
|
for (auto& button : mouse_button_devices) {
|
||||||
|
button.reset();
|
||||||
|
}
|
||||||
|
for (auto& analog : mouse_analog_devices) {
|
||||||
|
analog.reset();
|
||||||
|
}
|
||||||
|
mouse_stick_device.reset();
|
||||||
|
for (auto& button : keyboard_devices) {
|
||||||
|
button.reset();
|
||||||
|
}
|
||||||
|
for (auto& button : keyboard_modifier_devices) {
|
||||||
|
button.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::EnableConfiguration() {
|
||||||
|
is_configuring = true;
|
||||||
|
SaveCurrentConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::DisableConfiguration() {
|
||||||
|
is_configuring = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmulatedDevices::IsConfiguring() const {
|
||||||
|
return is_configuring;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SaveCurrentConfig() {
|
||||||
|
if (!is_configuring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::RestoreConfig() {
|
||||||
|
if (!is_configuring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReloadFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
|
||||||
|
std::size_t index) {
|
||||||
|
if (index >= device_status.keyboard_values.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
bool value_changed = false;
|
||||||
|
const auto new_status = TransformToButton(callback);
|
||||||
|
auto& current_status = device_status.keyboard_values[index];
|
||||||
|
current_status.toggle = new_status.toggle;
|
||||||
|
|
||||||
|
// Update button status with current status
|
||||||
|
if (!current_status.toggle) {
|
||||||
|
current_status.locked = false;
|
||||||
|
if (current_status.value != new_status.value) {
|
||||||
|
current_status.value = new_status.value;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Toggle button and lock status
|
||||||
|
if (new_status.value && !current_status.locked) {
|
||||||
|
current_status.locked = true;
|
||||||
|
current_status.value = !current_status.value;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock button, ready for next press
|
||||||
|
if (!new_status.value && current_status.locked) {
|
||||||
|
current_status.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value_changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
TriggerOnChange(DeviceTriggerType::Keyboard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index should be converted from NativeKeyboard to KeyboardKeyIndex
|
||||||
|
UpdateKey(index, current_status.value);
|
||||||
|
|
||||||
|
TriggerOnChange(DeviceTriggerType::Keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
|
||||||
|
constexpr std::size_t KEYS_PER_BYTE = 8;
|
||||||
|
auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
|
||||||
|
const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
|
||||||
|
if (status) {
|
||||||
|
entry = entry | mask;
|
||||||
|
} else {
|
||||||
|
entry = static_cast<u8>(entry & ~mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& callback,
|
||||||
|
std::size_t index) {
|
||||||
|
if (index >= device_status.keyboard_moddifier_values.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
bool value_changed = false;
|
||||||
|
const auto new_status = TransformToButton(callback);
|
||||||
|
auto& current_status = device_status.keyboard_moddifier_values[index];
|
||||||
|
current_status.toggle = new_status.toggle;
|
||||||
|
|
||||||
|
// Update button status with current
|
||||||
|
if (!current_status.toggle) {
|
||||||
|
current_status.locked = false;
|
||||||
|
if (current_status.value != new_status.value) {
|
||||||
|
current_status.value = new_status.value;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Toggle button and lock status
|
||||||
|
if (new_status.value && !current_status.locked) {
|
||||||
|
current_status.locked = true;
|
||||||
|
current_status.value = !current_status.value;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock button ready for next press
|
||||||
|
if (!new_status.value && current_status.locked) {
|
||||||
|
current_status.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value_changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case Settings::NativeKeyboard::LeftControl:
|
||||||
|
case Settings::NativeKeyboard::RightControl:
|
||||||
|
device_status.keyboard_moddifier_state.control.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeKeyboard::LeftShift:
|
||||||
|
case Settings::NativeKeyboard::RightShift:
|
||||||
|
device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeKeyboard::LeftAlt:
|
||||||
|
device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeKeyboard::RightAlt:
|
||||||
|
device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeKeyboard::CapsLock:
|
||||||
|
device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeKeyboard::ScrollLock:
|
||||||
|
device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeKeyboard::NumLock:
|
||||||
|
device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callback,
|
||||||
|
std::size_t index) {
|
||||||
|
if (index >= device_status.mouse_button_values.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
bool value_changed = false;
|
||||||
|
const auto new_status = TransformToButton(callback);
|
||||||
|
auto& current_status = device_status.mouse_button_values[index];
|
||||||
|
current_status.toggle = new_status.toggle;
|
||||||
|
|
||||||
|
// Update button status with current
|
||||||
|
if (!current_status.toggle) {
|
||||||
|
current_status.locked = false;
|
||||||
|
if (current_status.value != new_status.value) {
|
||||||
|
current_status.value = new_status.value;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Toggle button and lock status
|
||||||
|
if (new_status.value && !current_status.locked) {
|
||||||
|
current_status.locked = true;
|
||||||
|
current_status.value = !current_status.value;
|
||||||
|
value_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock button ready for next press
|
||||||
|
if (!new_status.value && current_status.locked) {
|
||||||
|
current_status.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value_changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case Settings::NativeMouseButton::Left:
|
||||||
|
device_status.mouse_button_state.left.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeMouseButton::Right:
|
||||||
|
device_status.mouse_button_state.right.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeMouseButton::Middle:
|
||||||
|
device_status.mouse_button_state.middle.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeMouseButton::Forward:
|
||||||
|
device_status.mouse_button_state.forward.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeMouseButton::Back:
|
||||||
|
device_status.mouse_button_state.back.Assign(current_status.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetMouseAnalog(const Common::Input::CallbackStatus& callback,
|
||||||
|
std::size_t index) {
|
||||||
|
if (index >= device_status.mouse_analog_values.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
const auto analog_value = TransformToAnalog(callback);
|
||||||
|
|
||||||
|
device_status.mouse_analog_values[index] = analog_value;
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
device_status.mouse_position_state = {};
|
||||||
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case Settings::NativeMouseWheel::X:
|
||||||
|
device_status.mouse_wheel_state.x = static_cast<s32>(analog_value.value);
|
||||||
|
break;
|
||||||
|
case Settings::NativeMouseWheel::Y:
|
||||||
|
device_status.mouse_wheel_state.y = static_cast<s32>(analog_value.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callback) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
const auto touch_value = TransformToTouch(callback);
|
||||||
|
|
||||||
|
device_status.mouse_stick_value = touch_value;
|
||||||
|
|
||||||
|
if (is_configuring) {
|
||||||
|
device_status.mouse_position_state = {};
|
||||||
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_status.mouse_position_state.x = touch_value.x.value;
|
||||||
|
device_status.mouse_position_state.y = touch_value.y.value;
|
||||||
|
|
||||||
|
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
|
||||||
|
return device_status.keyboard_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
|
||||||
|
return device_status.keyboard_moddifier_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
|
||||||
|
return device_status.mouse_button_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardKey EmulatedDevices::GetKeyboard() const {
|
||||||
|
return device_status.keyboard_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
|
||||||
|
return device_status.keyboard_moddifier_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseButton EmulatedDevices::GetMouseButtons() const {
|
||||||
|
return device_status.mouse_button_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
MousePosition EmulatedDevices::GetMousePosition() const {
|
||||||
|
return device_status.mouse_position_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogStickState EmulatedDevices::GetMouseWheel() const {
|
||||||
|
return device_status.mouse_wheel_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
||||||
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
const InterfaceUpdateCallback& poller = poller_pair.second;
|
||||||
|
if (poller.on_change) {
|
||||||
|
poller.on_change(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
|
||||||
|
return last_callback_key++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatedDevices::DeleteCallback(int key) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
const auto& iterator = callback_list.find(key);
|
||||||
|
if (iterator == callback_list.end()) {
|
||||||
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback_list.erase(iterator);
|
||||||
|
}
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/input.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "core/hid/hid_types.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||||
|
Settings::NativeKeyboard::NumKeyboardKeys>;
|
||||||
|
using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||||
|
Settings::NativeKeyboard::NumKeyboardMods>;
|
||||||
|
using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||||
|
Settings::NativeMouseButton::NumMouseButtons>;
|
||||||
|
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||||
|
Settings::NativeMouseWheel::NumMouseWheels>;
|
||||||
|
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||||
|
|
||||||
|
using MouseButtonParams =
|
||||||
|
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
|
||||||
|
|
||||||
|
using KeyboardValues =
|
||||||
|
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
|
||||||
|
using KeyboardModifierValues =
|
||||||
|
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
|
||||||
|
using MouseButtonValues =
|
||||||
|
std::array<Common::Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
|
||||||
|
using MouseAnalogValues =
|
||||||
|
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
|
||||||
|
using MouseStickValue = Common::Input::TouchStatus;
|
||||||
|
|
||||||
|
struct MousePosition {
|
||||||
|
f32 x;
|
||||||
|
f32 y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeviceStatus {
|
||||||
|
// Data from input_common
|
||||||
|
KeyboardValues keyboard_values{};
|
||||||
|
KeyboardModifierValues keyboard_moddifier_values{};
|
||||||
|
MouseButtonValues mouse_button_values{};
|
||||||
|
MouseAnalogValues mouse_analog_values{};
|
||||||
|
MouseStickValue mouse_stick_value{};
|
||||||
|
|
||||||
|
// Data for HID serices
|
||||||
|
KeyboardKey keyboard_state{};
|
||||||
|
KeyboardModifier keyboard_moddifier_state{};
|
||||||
|
MouseButton mouse_button_state{};
|
||||||
|
MousePosition mouse_position_state{};
|
||||||
|
AnalogStickState mouse_wheel_state{};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DeviceTriggerType {
|
||||||
|
Keyboard,
|
||||||
|
KeyboardModdifier,
|
||||||
|
Mouse,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InterfaceUpdateCallback {
|
||||||
|
std::function<void(DeviceTriggerType)> on_change;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EmulatedDevices {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Contains all input data related to external devices that aren't necesarily a controller
|
||||||
|
* This includes devices such as the keyboard or mouse
|
||||||
|
*/
|
||||||
|
explicit EmulatedDevices();
|
||||||
|
~EmulatedDevices();
|
||||||
|
|
||||||
|
YUZU_NON_COPYABLE(EmulatedDevices);
|
||||||
|
YUZU_NON_MOVEABLE(EmulatedDevices);
|
||||||
|
|
||||||
|
/// Removes all callbacks created from input devices
|
||||||
|
void UnloadInput();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the emulated devices into configuring mode
|
||||||
|
* This prevents the modification of the HID state of the emulated devices by input commands
|
||||||
|
*/
|
||||||
|
void EnableConfiguration();
|
||||||
|
|
||||||
|
/// Returns the emulated devices into normal mode, allowing the modification of the HID state
|
||||||
|
void DisableConfiguration();
|
||||||
|
|
||||||
|
/// Returns true if the emulated device is in configuring mode
|
||||||
|
bool IsConfiguring() const;
|
||||||
|
|
||||||
|
/// Reload all input devices
|
||||||
|
void ReloadInput();
|
||||||
|
|
||||||
|
/// Overrides current mapped devices with the stored configuration and reloads all input devices
|
||||||
|
void ReloadFromSettings();
|
||||||
|
|
||||||
|
/// Saves the current mapped configuration
|
||||||
|
void SaveCurrentConfig();
|
||||||
|
|
||||||
|
/// Reverts any mapped changes made that weren't saved
|
||||||
|
void RestoreConfig();
|
||||||
|
|
||||||
|
/// Returns the latest status of button input from the keyboard with parameters
|
||||||
|
KeyboardValues GetKeyboardValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input from the keyboard modifiers with parameters
|
||||||
|
KeyboardModifierValues GetKeyboardModdifierValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input from the mouse with parameters
|
||||||
|
MouseButtonValues GetMouseButtonsValues() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input from the keyboard
|
||||||
|
KeyboardKey GetKeyboard() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input from the keyboard modifiers
|
||||||
|
KeyboardModifier GetKeyboardModifier() const;
|
||||||
|
|
||||||
|
/// Returns the latest status of button input from the mouse
|
||||||
|
MouseButton GetMouseButtons() const;
|
||||||
|
|
||||||
|
/// Returns the latest mouse coordinates
|
||||||
|
MousePosition GetMousePosition() const;
|
||||||
|
|
||||||
|
/// Returns the latest mouse wheel change
|
||||||
|
AnalogStickState GetMouseWheel() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a callback to the list of events
|
||||||
|
* @param update_callback InterfaceUpdateCallback that will be triggered
|
||||||
|
* @return an unique key corresponding to the callback index in the list
|
||||||
|
*/
|
||||||
|
int SetCallback(InterfaceUpdateCallback update_callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a callback from the list stopping any future events to this object
|
||||||
|
* @param key Key corresponding to the callback index in the list
|
||||||
|
*/
|
||||||
|
void DeleteCallback(int key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Helps assigning a value to keyboard_state
|
||||||
|
void UpdateKey(std::size_t key_index, bool status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the touch status of the keyboard device
|
||||||
|
* @param callback A CallbackStatus containing the key status
|
||||||
|
* @param index key ID to be updated
|
||||||
|
*/
|
||||||
|
void SetKeyboardButton(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the keyboard status of the keyboard device
|
||||||
|
* @param callback A CallbackStatus containing the modifier key status
|
||||||
|
* @param index modifier key ID to be updated
|
||||||
|
*/
|
||||||
|
void SetKeyboardModifier(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the mouse button status of the mouse device
|
||||||
|
* @param callback A CallbackStatus containing the button status
|
||||||
|
* @param index Button ID to be updated
|
||||||
|
*/
|
||||||
|
void SetMouseButton(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the mouse wheel status of the mouse device
|
||||||
|
* @param callback A CallbackStatus containing the wheel status
|
||||||
|
* @param index wheel ID to be updated
|
||||||
|
*/
|
||||||
|
void SetMouseAnalog(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the mouse position status of the mouse device
|
||||||
|
* @param callback A CallbackStatus containing the position status
|
||||||
|
*/
|
||||||
|
void SetMouseStick(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a callback that something has changed on the device status
|
||||||
|
* @param type Input type of the event to trigger
|
||||||
|
*/
|
||||||
|
void TriggerOnChange(DeviceTriggerType type);
|
||||||
|
|
||||||
|
bool is_configuring{false};
|
||||||
|
|
||||||
|
KeyboardDevices keyboard_devices;
|
||||||
|
KeyboardModifierDevices keyboard_modifier_devices;
|
||||||
|
MouseButtonDevices mouse_button_devices;
|
||||||
|
MouseAnalogDevices mouse_analog_devices;
|
||||||
|
MouseStickDevice mouse_stick_device;
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
std::unordered_map<int, InterfaceUpdateCallback> callback_list;
|
||||||
|
int last_callback_key = 0;
|
||||||
|
|
||||||
|
// Stores the current status of all external device input
|
||||||
|
DeviceStatus device_status;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,214 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "core/hid/emulated_console.h"
|
||||||
|
#include "core/hid/emulated_controller.h"
|
||||||
|
#include "core/hid/emulated_devices.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
HIDCore::HIDCore()
|
||||||
|
: player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
|
||||||
|
player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
|
||||||
|
player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
|
||||||
|
player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
|
||||||
|
player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
|
||||||
|
player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
|
||||||
|
player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
|
||||||
|
player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
|
||||||
|
other{std::make_unique<EmulatedController>(NpadIdType::Other)},
|
||||||
|
handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
|
||||||
|
console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
|
||||||
|
|
||||||
|
HIDCore::~HIDCore() = default;
|
||||||
|
|
||||||
|
EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
|
||||||
|
switch (npad_id_type) {
|
||||||
|
case NpadIdType::Player1:
|
||||||
|
return player_1.get();
|
||||||
|
case NpadIdType::Player2:
|
||||||
|
return player_2.get();
|
||||||
|
case NpadIdType::Player3:
|
||||||
|
return player_3.get();
|
||||||
|
case NpadIdType::Player4:
|
||||||
|
return player_4.get();
|
||||||
|
case NpadIdType::Player5:
|
||||||
|
return player_5.get();
|
||||||
|
case NpadIdType::Player6:
|
||||||
|
return player_6.get();
|
||||||
|
case NpadIdType::Player7:
|
||||||
|
return player_7.get();
|
||||||
|
case NpadIdType::Player8:
|
||||||
|
return player_8.get();
|
||||||
|
case NpadIdType::Other:
|
||||||
|
return other.get();
|
||||||
|
case NpadIdType::Handheld:
|
||||||
|
return handheld.get();
|
||||||
|
case NpadIdType::Invalid:
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
|
||||||
|
switch (npad_id_type) {
|
||||||
|
case NpadIdType::Player1:
|
||||||
|
return player_1.get();
|
||||||
|
case NpadIdType::Player2:
|
||||||
|
return player_2.get();
|
||||||
|
case NpadIdType::Player3:
|
||||||
|
return player_3.get();
|
||||||
|
case NpadIdType::Player4:
|
||||||
|
return player_4.get();
|
||||||
|
case NpadIdType::Player5:
|
||||||
|
return player_5.get();
|
||||||
|
case NpadIdType::Player6:
|
||||||
|
return player_6.get();
|
||||||
|
case NpadIdType::Player7:
|
||||||
|
return player_7.get();
|
||||||
|
case NpadIdType::Player8:
|
||||||
|
return player_8.get();
|
||||||
|
case NpadIdType::Other:
|
||||||
|
return other.get();
|
||||||
|
case NpadIdType::Handheld:
|
||||||
|
return handheld.get();
|
||||||
|
case NpadIdType::Invalid:
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EmulatedConsole* HIDCore::GetEmulatedConsole() {
|
||||||
|
return console.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
|
||||||
|
return console.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmulatedDevices* HIDCore::GetEmulatedDevices() {
|
||||||
|
return devices.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
|
||||||
|
return devices.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
|
||||||
|
return GetEmulatedController(IndexToNpadIdType(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
|
||||||
|
return GetEmulatedController(IndexToNpadIdType(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
|
||||||
|
supported_style_tag.raw = style_tag.raw;
|
||||||
|
player_1->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_2->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_3->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_4->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_5->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_6->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_7->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
player_8->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
other->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
handheld->SetSupportedNpadStyleTag(supported_style_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
NpadStyleTag HIDCore::GetSupportedStyleTag() const {
|
||||||
|
return supported_style_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
s8 HIDCore::GetPlayerCount() const {
|
||||||
|
s8 active_players = 0;
|
||||||
|
for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
|
||||||
|
const auto* const controller = GetEmulatedControllerByIndex(player_index);
|
||||||
|
if (controller->IsConnected()) {
|
||||||
|
active_players++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return active_players;
|
||||||
|
}
|
||||||
|
|
||||||
|
NpadIdType HIDCore::GetFirstNpadId() const {
|
||||||
|
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
|
||||||
|
const auto* const controller = GetEmulatedControllerByIndex(player_index);
|
||||||
|
if (controller->IsConnected()) {
|
||||||
|
return controller->GetNpadIdType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NpadIdType::Player1;
|
||||||
|
}
|
||||||
|
|
||||||
|
NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
|
||||||
|
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
|
||||||
|
const auto* const controller = GetEmulatedControllerByIndex(player_index);
|
||||||
|
if (!controller->IsConnected()) {
|
||||||
|
return controller->GetNpadIdType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NpadIdType::Player1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HIDCore::EnableAllControllerConfiguration() {
|
||||||
|
player_1->EnableConfiguration();
|
||||||
|
player_2->EnableConfiguration();
|
||||||
|
player_3->EnableConfiguration();
|
||||||
|
player_4->EnableConfiguration();
|
||||||
|
player_5->EnableConfiguration();
|
||||||
|
player_6->EnableConfiguration();
|
||||||
|
player_7->EnableConfiguration();
|
||||||
|
player_8->EnableConfiguration();
|
||||||
|
other->EnableConfiguration();
|
||||||
|
handheld->EnableConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HIDCore::DisableAllControllerConfiguration() {
|
||||||
|
player_1->DisableConfiguration();
|
||||||
|
player_2->DisableConfiguration();
|
||||||
|
player_3->DisableConfiguration();
|
||||||
|
player_4->DisableConfiguration();
|
||||||
|
player_5->DisableConfiguration();
|
||||||
|
player_6->DisableConfiguration();
|
||||||
|
player_7->DisableConfiguration();
|
||||||
|
player_8->DisableConfiguration();
|
||||||
|
other->DisableConfiguration();
|
||||||
|
handheld->DisableConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HIDCore::ReloadInputDevices() {
|
||||||
|
player_1->ReloadFromSettings();
|
||||||
|
player_2->ReloadFromSettings();
|
||||||
|
player_3->ReloadFromSettings();
|
||||||
|
player_4->ReloadFromSettings();
|
||||||
|
player_5->ReloadFromSettings();
|
||||||
|
player_6->ReloadFromSettings();
|
||||||
|
player_7->ReloadFromSettings();
|
||||||
|
player_8->ReloadFromSettings();
|
||||||
|
other->ReloadFromSettings();
|
||||||
|
handheld->ReloadFromSettings();
|
||||||
|
console->ReloadFromSettings();
|
||||||
|
devices->ReloadFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HIDCore::UnloadInputDevices() {
|
||||||
|
player_1->UnloadInput();
|
||||||
|
player_2->UnloadInput();
|
||||||
|
player_3->UnloadInput();
|
||||||
|
player_4->UnloadInput();
|
||||||
|
player_5->UnloadInput();
|
||||||
|
player_6->UnloadInput();
|
||||||
|
player_7->UnloadInput();
|
||||||
|
player_8->UnloadInput();
|
||||||
|
other->UnloadInput();
|
||||||
|
handheld->UnloadInput();
|
||||||
|
console->UnloadInput();
|
||||||
|
devices->UnloadInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "core/hid/hid_types.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class EmulatedConsole;
|
||||||
|
class EmulatedController;
|
||||||
|
class EmulatedDevices;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
class HIDCore {
|
||||||
|
public:
|
||||||
|
explicit HIDCore();
|
||||||
|
~HIDCore();
|
||||||
|
|
||||||
|
YUZU_NON_COPYABLE(HIDCore);
|
||||||
|
YUZU_NON_MOVEABLE(HIDCore);
|
||||||
|
|
||||||
|
EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
|
||||||
|
const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
|
||||||
|
|
||||||
|
EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
|
||||||
|
const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
|
||||||
|
|
||||||
|
EmulatedConsole* GetEmulatedConsole();
|
||||||
|
const EmulatedConsole* GetEmulatedConsole() const;
|
||||||
|
|
||||||
|
EmulatedDevices* GetEmulatedDevices();
|
||||||
|
const EmulatedDevices* GetEmulatedDevices() const;
|
||||||
|
|
||||||
|
void SetSupportedStyleTag(NpadStyleTag style_tag);
|
||||||
|
NpadStyleTag GetSupportedStyleTag() const;
|
||||||
|
|
||||||
|
/// Counts the connected players from P1-P8
|
||||||
|
s8 GetPlayerCount() const;
|
||||||
|
|
||||||
|
/// Returns the first connected npad id
|
||||||
|
NpadIdType GetFirstNpadId() const;
|
||||||
|
|
||||||
|
/// Returns the first disconnected npad id
|
||||||
|
NpadIdType GetFirstDisconnectedNpadId() const;
|
||||||
|
|
||||||
|
/// Sets all emulated controllers into configuring mode.
|
||||||
|
void EnableAllControllerConfiguration();
|
||||||
|
|
||||||
|
/// Sets all emulated controllers into normal mode.
|
||||||
|
void DisableAllControllerConfiguration();
|
||||||
|
|
||||||
|
/// Reloads all input devices from settings
|
||||||
|
void ReloadInputDevices();
|
||||||
|
|
||||||
|
/// Removes all callbacks from input common
|
||||||
|
void UnloadInputDevices();
|
||||||
|
|
||||||
|
/// Number of emulated controllers
|
||||||
|
static constexpr std::size_t available_controllers{10};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<EmulatedController> player_1;
|
||||||
|
std::unique_ptr<EmulatedController> player_2;
|
||||||
|
std::unique_ptr<EmulatedController> player_3;
|
||||||
|
std::unique_ptr<EmulatedController> player_4;
|
||||||
|
std::unique_ptr<EmulatedController> player_5;
|
||||||
|
std::unique_ptr<EmulatedController> player_6;
|
||||||
|
std::unique_ptr<EmulatedController> player_7;
|
||||||
|
std::unique_ptr<EmulatedController> player_8;
|
||||||
|
std::unique_ptr<EmulatedController> other;
|
||||||
|
std::unique_ptr<EmulatedController> handheld;
|
||||||
|
std::unique_ptr<EmulatedConsole> console;
|
||||||
|
std::unique_ptr<EmulatedDevices> devices;
|
||||||
|
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,635 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/point.h"
|
||||||
|
#include "common/uuid.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
enum class DeviceIndex : u8 {
|
||||||
|
Left = 0,
|
||||||
|
Right = 1,
|
||||||
|
None = 2,
|
||||||
|
MaxDeviceIndex = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::NpadButton
|
||||||
|
enum class NpadButton : u64 {
|
||||||
|
None = 0,
|
||||||
|
A = 1U << 0,
|
||||||
|
B = 1U << 1,
|
||||||
|
X = 1U << 2,
|
||||||
|
Y = 1U << 3,
|
||||||
|
StickL = 1U << 4,
|
||||||
|
StickR = 1U << 5,
|
||||||
|
L = 1U << 6,
|
||||||
|
R = 1U << 7,
|
||||||
|
ZL = 1U << 8,
|
||||||
|
ZR = 1U << 9,
|
||||||
|
Plus = 1U << 10,
|
||||||
|
Minus = 1U << 11,
|
||||||
|
|
||||||
|
Left = 1U << 12,
|
||||||
|
Up = 1U << 13,
|
||||||
|
Right = 1U << 14,
|
||||||
|
Down = 1U << 15,
|
||||||
|
|
||||||
|
StickLLeft = 1U << 16,
|
||||||
|
StickLUp = 1U << 17,
|
||||||
|
StickLRight = 1U << 18,
|
||||||
|
StickLDown = 1U << 19,
|
||||||
|
|
||||||
|
StickRLeft = 1U << 20,
|
||||||
|
StickRUp = 1U << 21,
|
||||||
|
StickRRight = 1U << 22,
|
||||||
|
StickRDown = 1U << 23,
|
||||||
|
|
||||||
|
LeftSL = 1U << 24,
|
||||||
|
LeftSR = 1U << 25,
|
||||||
|
|
||||||
|
RightSL = 1U << 26,
|
||||||
|
RightSR = 1U << 27,
|
||||||
|
|
||||||
|
Palma = 1U << 28,
|
||||||
|
Verification = 1U << 29,
|
||||||
|
HandheldLeftB = 1U << 30,
|
||||||
|
LagonCLeft = 1U << 31,
|
||||||
|
LagonCUp = 1ULL << 32,
|
||||||
|
LagonCRight = 1ULL << 33,
|
||||||
|
LagonCDown = 1ULL << 34,
|
||||||
|
|
||||||
|
All = 0xFFFFFFFFFFFFFFFFULL,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(NpadButton);
|
||||||
|
|
||||||
|
enum class KeyboardKeyIndex : u32 {
|
||||||
|
A = 4,
|
||||||
|
B = 5,
|
||||||
|
C = 6,
|
||||||
|
D = 7,
|
||||||
|
E = 8,
|
||||||
|
F = 9,
|
||||||
|
G = 10,
|
||||||
|
H = 11,
|
||||||
|
I = 12,
|
||||||
|
J = 13,
|
||||||
|
K = 14,
|
||||||
|
L = 15,
|
||||||
|
M = 16,
|
||||||
|
N = 17,
|
||||||
|
O = 18,
|
||||||
|
P = 19,
|
||||||
|
Q = 20,
|
||||||
|
R = 21,
|
||||||
|
S = 22,
|
||||||
|
T = 23,
|
||||||
|
U = 24,
|
||||||
|
V = 25,
|
||||||
|
W = 26,
|
||||||
|
X = 27,
|
||||||
|
Y = 28,
|
||||||
|
Z = 29,
|
||||||
|
D1 = 30,
|
||||||
|
D2 = 31,
|
||||||
|
D3 = 32,
|
||||||
|
D4 = 33,
|
||||||
|
D5 = 34,
|
||||||
|
D6 = 35,
|
||||||
|
D7 = 36,
|
||||||
|
D8 = 37,
|
||||||
|
D9 = 38,
|
||||||
|
D0 = 39,
|
||||||
|
Return = 40,
|
||||||
|
Escape = 41,
|
||||||
|
Backspace = 42,
|
||||||
|
Tab = 43,
|
||||||
|
Space = 44,
|
||||||
|
Minus = 45,
|
||||||
|
Plus = 46,
|
||||||
|
OpenBracket = 47,
|
||||||
|
CloseBracket = 48,
|
||||||
|
Pipe = 49,
|
||||||
|
Tilde = 50,
|
||||||
|
Semicolon = 51,
|
||||||
|
Quote = 52,
|
||||||
|
Backquote = 53,
|
||||||
|
Comma = 54,
|
||||||
|
Period = 55,
|
||||||
|
Slash = 56,
|
||||||
|
CapsLock = 57,
|
||||||
|
F1 = 58,
|
||||||
|
F2 = 59,
|
||||||
|
F3 = 60,
|
||||||
|
F4 = 61,
|
||||||
|
F5 = 62,
|
||||||
|
F6 = 63,
|
||||||
|
F7 = 64,
|
||||||
|
F8 = 65,
|
||||||
|
F9 = 66,
|
||||||
|
F10 = 67,
|
||||||
|
F11 = 68,
|
||||||
|
F12 = 69,
|
||||||
|
PrintScreen = 70,
|
||||||
|
ScrollLock = 71,
|
||||||
|
Pause = 72,
|
||||||
|
Insert = 73,
|
||||||
|
Home = 74,
|
||||||
|
PageUp = 75,
|
||||||
|
Delete = 76,
|
||||||
|
End = 77,
|
||||||
|
PageDown = 78,
|
||||||
|
RightArrow = 79,
|
||||||
|
LeftArrow = 80,
|
||||||
|
DownArrow = 81,
|
||||||
|
UpArrow = 82,
|
||||||
|
NumLock = 83,
|
||||||
|
NumPadDivide = 84,
|
||||||
|
NumPadMultiply = 85,
|
||||||
|
NumPadSubtract = 86,
|
||||||
|
NumPadAdd = 87,
|
||||||
|
NumPadEnter = 88,
|
||||||
|
NumPad1 = 89,
|
||||||
|
NumPad2 = 90,
|
||||||
|
NumPad3 = 91,
|
||||||
|
NumPad4 = 92,
|
||||||
|
NumPad5 = 93,
|
||||||
|
NumPad6 = 94,
|
||||||
|
NumPad7 = 95,
|
||||||
|
NumPad8 = 96,
|
||||||
|
NumPad9 = 97,
|
||||||
|
NumPad0 = 98,
|
||||||
|
NumPadDot = 99,
|
||||||
|
Backslash = 100,
|
||||||
|
Application = 101,
|
||||||
|
Power = 102,
|
||||||
|
NumPadEquals = 103,
|
||||||
|
F13 = 104,
|
||||||
|
F14 = 105,
|
||||||
|
F15 = 106,
|
||||||
|
F16 = 107,
|
||||||
|
F17 = 108,
|
||||||
|
F18 = 109,
|
||||||
|
F19 = 110,
|
||||||
|
F20 = 111,
|
||||||
|
F21 = 112,
|
||||||
|
F22 = 113,
|
||||||
|
F23 = 114,
|
||||||
|
F24 = 115,
|
||||||
|
NumPadComma = 133,
|
||||||
|
Ro = 135,
|
||||||
|
KatakanaHiragana = 136,
|
||||||
|
Yen = 137,
|
||||||
|
Henkan = 138,
|
||||||
|
Muhenkan = 139,
|
||||||
|
NumPadCommaPc98 = 140,
|
||||||
|
HangulEnglish = 144,
|
||||||
|
Hanja = 145,
|
||||||
|
Katakana = 146,
|
||||||
|
Hiragana = 147,
|
||||||
|
ZenkakuHankaku = 148,
|
||||||
|
LeftControl = 224,
|
||||||
|
LeftShift = 225,
|
||||||
|
LeftAlt = 226,
|
||||||
|
LeftGui = 227,
|
||||||
|
RightControl = 228,
|
||||||
|
RightShift = 229,
|
||||||
|
RightAlt = 230,
|
||||||
|
RightGui = 231,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::NpadIdType
|
||||||
|
enum class NpadIdType : u32 {
|
||||||
|
Player1 = 0x0,
|
||||||
|
Player2 = 0x1,
|
||||||
|
Player3 = 0x2,
|
||||||
|
Player4 = 0x3,
|
||||||
|
Player5 = 0x4,
|
||||||
|
Player6 = 0x5,
|
||||||
|
Player7 = 0x6,
|
||||||
|
Player8 = 0x7,
|
||||||
|
Other = 0x10,
|
||||||
|
Handheld = 0x20,
|
||||||
|
|
||||||
|
Invalid = 0xFFFFFFFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::NpadStyleIndex
|
||||||
|
enum class NpadStyleIndex : u8 {
|
||||||
|
None = 0,
|
||||||
|
ProController = 3,
|
||||||
|
Handheld = 4,
|
||||||
|
HandheldNES = 4,
|
||||||
|
JoyconDual = 5,
|
||||||
|
JoyconLeft = 6,
|
||||||
|
JoyconRight = 7,
|
||||||
|
GameCube = 8,
|
||||||
|
Pokeball = 9,
|
||||||
|
NES = 10,
|
||||||
|
SNES = 12,
|
||||||
|
N64 = 13,
|
||||||
|
SegaGenesis = 14,
|
||||||
|
SystemExt = 32,
|
||||||
|
System = 33,
|
||||||
|
MaxNpadType = 34,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::NpadStyleSet
|
||||||
|
enum class NpadStyleSet : u32 {
|
||||||
|
None = 0,
|
||||||
|
Fullkey = 1U << 0,
|
||||||
|
Handheld = 1U << 1,
|
||||||
|
JoyDual = 1U << 2,
|
||||||
|
JoyLeft = 1U << 3,
|
||||||
|
JoyRight = 1U << 4,
|
||||||
|
Gc = 1U << 5,
|
||||||
|
Palma = 1U << 6,
|
||||||
|
Lark = 1U << 7,
|
||||||
|
HandheldLark = 1U << 8,
|
||||||
|
Lucia = 1U << 9,
|
||||||
|
Lagoon = 1U << 10,
|
||||||
|
Lager = 1U << 11,
|
||||||
|
SystemExt = 1U << 29,
|
||||||
|
System = 1U << 30,
|
||||||
|
|
||||||
|
All = 0xFFFFFFFFU,
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::VibrationDevicePosition
|
||||||
|
enum class VibrationDevicePosition : u32 {
|
||||||
|
None = 0,
|
||||||
|
Left = 1,
|
||||||
|
Right = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::VibrationDeviceType
|
||||||
|
enum class VibrationDeviceType : u32 {
|
||||||
|
Unknown = 0,
|
||||||
|
LinearResonantActuator = 1,
|
||||||
|
GcErm = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::VibrationGcErmCommand
|
||||||
|
enum class VibrationGcErmCommand : u64 {
|
||||||
|
Stop = 0,
|
||||||
|
Start = 1,
|
||||||
|
StopHard = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::hid::NpadStyleTag
|
||||||
|
struct NpadStyleTag {
|
||||||
|
union {
|
||||||
|
NpadStyleSet raw{};
|
||||||
|
|
||||||
|
BitField<0, 1, u32> fullkey;
|
||||||
|
BitField<1, 1, u32> handheld;
|
||||||
|
BitField<2, 1, u32> joycon_dual;
|
||||||
|
BitField<3, 1, u32> joycon_left;
|
||||||
|
BitField<4, 1, u32> joycon_right;
|
||||||
|
BitField<5, 1, u32> gamecube;
|
||||||
|
BitField<6, 1, u32> palma;
|
||||||
|
BitField<7, 1, u32> lark;
|
||||||
|
BitField<8, 1, u32> handheld_lark;
|
||||||
|
BitField<9, 1, u32> lucia;
|
||||||
|
BitField<10, 1, u32> lagoon;
|
||||||
|
BitField<11, 1, u32> lager;
|
||||||
|
BitField<29, 1, u32> system_ext;
|
||||||
|
BitField<30, 1, u32> system;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::TouchAttribute
|
||||||
|
struct TouchAttribute {
|
||||||
|
union {
|
||||||
|
u32 raw{};
|
||||||
|
BitField<0, 1, u32> start_touch;
|
||||||
|
BitField<1, 1, u32> end_touch;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::TouchState
|
||||||
|
struct TouchState {
|
||||||
|
u64 delta_time;
|
||||||
|
TouchAttribute attribute;
|
||||||
|
u32 finger;
|
||||||
|
Common::Point<u32> position;
|
||||||
|
u32 diameter_x;
|
||||||
|
u32 diameter_y;
|
||||||
|
u32 rotation_angle;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::NpadControllerColor
|
||||||
|
struct NpadControllerColor {
|
||||||
|
u32 body;
|
||||||
|
u32 button;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::AnalogStickState
|
||||||
|
struct AnalogStickState {
|
||||||
|
s32 x;
|
||||||
|
s32 y;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::server::NpadGcTriggerState
|
||||||
|
struct NpadGcTriggerState {
|
||||||
|
s64 sampling_number{};
|
||||||
|
s32 left{};
|
||||||
|
s32 right{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::system::NpadBatteryLevel
|
||||||
|
using NpadBatteryLevel = u32;
|
||||||
|
static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::system::NpadPowerInfo
|
||||||
|
struct NpadPowerInfo {
|
||||||
|
bool is_powered;
|
||||||
|
bool is_charging;
|
||||||
|
INSERT_PADDING_BYTES(0x6);
|
||||||
|
NpadBatteryLevel battery_level;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
|
||||||
|
|
||||||
|
struct LedPattern {
|
||||||
|
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
|
||||||
|
position1.Assign(light1);
|
||||||
|
position2.Assign(light2);
|
||||||
|
position3.Assign(light3);
|
||||||
|
position4.Assign(light4);
|
||||||
|
}
|
||||||
|
union {
|
||||||
|
u64 raw{};
|
||||||
|
BitField<0, 1, u64> position1;
|
||||||
|
BitField<1, 1, u64> position2;
|
||||||
|
BitField<2, 1, u64> position3;
|
||||||
|
BitField<3, 1, u64> position4;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NpadButtonState {
|
||||||
|
union {
|
||||||
|
NpadButton raw{};
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
BitField<0, 1, u64> a;
|
||||||
|
BitField<1, 1, u64> b;
|
||||||
|
BitField<2, 1, u64> x;
|
||||||
|
BitField<3, 1, u64> y;
|
||||||
|
BitField<4, 1, u64> stick_l;
|
||||||
|
BitField<5, 1, u64> stick_r;
|
||||||
|
BitField<6, 1, u64> l;
|
||||||
|
BitField<7, 1, u64> r;
|
||||||
|
BitField<8, 1, u64> zl;
|
||||||
|
BitField<9, 1, u64> zr;
|
||||||
|
BitField<10, 1, u64> plus;
|
||||||
|
BitField<11, 1, u64> minus;
|
||||||
|
|
||||||
|
// D-Pad
|
||||||
|
BitField<12, 1, u64> left;
|
||||||
|
BitField<13, 1, u64> up;
|
||||||
|
BitField<14, 1, u64> right;
|
||||||
|
BitField<15, 1, u64> down;
|
||||||
|
|
||||||
|
// Left JoyStick
|
||||||
|
BitField<16, 1, u64> stick_l_left;
|
||||||
|
BitField<17, 1, u64> stick_l_up;
|
||||||
|
BitField<18, 1, u64> stick_l_right;
|
||||||
|
BitField<19, 1, u64> stick_l_down;
|
||||||
|
|
||||||
|
// Right JoyStick
|
||||||
|
BitField<20, 1, u64> stick_r_left;
|
||||||
|
BitField<21, 1, u64> stick_r_up;
|
||||||
|
BitField<22, 1, u64> stick_r_right;
|
||||||
|
BitField<23, 1, u64> stick_r_down;
|
||||||
|
|
||||||
|
BitField<24, 1, u64> left_sl;
|
||||||
|
BitField<25, 1, u64> left_sr;
|
||||||
|
|
||||||
|
BitField<26, 1, u64> right_sl;
|
||||||
|
BitField<27, 1, u64> right_sr;
|
||||||
|
|
||||||
|
BitField<28, 1, u64> palma;
|
||||||
|
BitField<29, 1, u64> verification;
|
||||||
|
BitField<30, 1, u64> handheld_left_b;
|
||||||
|
BitField<31, 1, u64> lagon_c_left;
|
||||||
|
BitField<32, 1, u64> lagon_c_up;
|
||||||
|
BitField<33, 1, u64> lagon_c_right;
|
||||||
|
BitField<34, 1, u64> lagon_c_down;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size.");
|
||||||
|
|
||||||
|
// This is nn::hid::DebugPadButton
|
||||||
|
struct DebugPadButton {
|
||||||
|
union {
|
||||||
|
u32 raw{};
|
||||||
|
BitField<0, 1, u32> a;
|
||||||
|
BitField<1, 1, u32> b;
|
||||||
|
BitField<2, 1, u32> x;
|
||||||
|
BitField<3, 1, u32> y;
|
||||||
|
BitField<4, 1, u32> l;
|
||||||
|
BitField<5, 1, u32> r;
|
||||||
|
BitField<6, 1, u32> zl;
|
||||||
|
BitField<7, 1, u32> zr;
|
||||||
|
BitField<8, 1, u32> plus;
|
||||||
|
BitField<9, 1, u32> minus;
|
||||||
|
BitField<10, 1, u32> d_left;
|
||||||
|
BitField<11, 1, u32> d_up;
|
||||||
|
BitField<12, 1, u32> d_right;
|
||||||
|
BitField<13, 1, u32> d_down;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::ConsoleSixAxisSensorHandle
|
||||||
|
struct ConsoleSixAxisSensorHandle {
|
||||||
|
u8 unknown_1;
|
||||||
|
u8 unknown_2;
|
||||||
|
INSERT_PADDING_BYTES_NOINIT(2);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
|
||||||
|
"ConsoleSixAxisSensorHandle is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::SixAxisSensorHandle
|
||||||
|
struct SixAxisSensorHandle {
|
||||||
|
NpadStyleIndex npad_type;
|
||||||
|
u8 npad_id;
|
||||||
|
DeviceIndex device_index;
|
||||||
|
INSERT_PADDING_BYTES_NOINIT(1);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size");
|
||||||
|
|
||||||
|
struct SixAxisSensorFusionParameters {
|
||||||
|
f32 parameter1;
|
||||||
|
f32 parameter2;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SixAxisSensorFusionParameters) == 8,
|
||||||
|
"SixAxisSensorFusionParameters is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::VibrationDeviceHandle
|
||||||
|
struct VibrationDeviceHandle {
|
||||||
|
NpadStyleIndex npad_type;
|
||||||
|
u8 npad_id;
|
||||||
|
DeviceIndex device_index;
|
||||||
|
INSERT_PADDING_BYTES_NOINIT(1);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::VibrationValue
|
||||||
|
struct VibrationValue {
|
||||||
|
f32 low_amplitude;
|
||||||
|
f32 low_frequency;
|
||||||
|
f32 high_amplitude;
|
||||||
|
f32 high_frequency;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
|
||||||
|
|
||||||
|
// This is nn::hid::VibrationDeviceInfo
|
||||||
|
struct VibrationDeviceInfo {
|
||||||
|
VibrationDeviceType type{};
|
||||||
|
VibrationDevicePosition position{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
|
||||||
|
|
||||||
|
// This is nn::hid::KeyboardModifier
|
||||||
|
struct KeyboardModifier {
|
||||||
|
union {
|
||||||
|
u32 raw{};
|
||||||
|
BitField<0, 1, u32> control;
|
||||||
|
BitField<1, 1, u32> shift;
|
||||||
|
BitField<2, 1, u32> left_alt;
|
||||||
|
BitField<3, 1, u32> right_alt;
|
||||||
|
BitField<4, 1, u32> gui;
|
||||||
|
BitField<8, 1, u32> caps_lock;
|
||||||
|
BitField<9, 1, u32> scroll_lock;
|
||||||
|
BitField<10, 1, u32> num_lock;
|
||||||
|
BitField<11, 1, u32> katakana;
|
||||||
|
BitField<12, 1, u32> hiragana;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::KeyboardAttribute
|
||||||
|
struct KeyboardAttribute {
|
||||||
|
union {
|
||||||
|
u32 raw{};
|
||||||
|
BitField<0, 1, u32> is_connected;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::KeyboardKey
|
||||||
|
struct KeyboardKey {
|
||||||
|
// This should be a 256 bit flag
|
||||||
|
std::array<u8, 32> key;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::MouseButton
|
||||||
|
struct MouseButton {
|
||||||
|
union {
|
||||||
|
u32_le raw{};
|
||||||
|
BitField<0, 1, u32> left;
|
||||||
|
BitField<1, 1, u32> right;
|
||||||
|
BitField<2, 1, u32> middle;
|
||||||
|
BitField<3, 1, u32> forward;
|
||||||
|
BitField<4, 1, u32> back;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::MouseAttribute
|
||||||
|
struct MouseAttribute {
|
||||||
|
union {
|
||||||
|
u32 raw{};
|
||||||
|
BitField<0, 1, u32> transferable;
|
||||||
|
BitField<1, 1, u32> is_connected;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::hid::detail::MouseState
|
||||||
|
struct MouseState {
|
||||||
|
s64 sampling_number;
|
||||||
|
s32 x;
|
||||||
|
s32 y;
|
||||||
|
s32 delta_x;
|
||||||
|
s32 delta_y;
|
||||||
|
// Axis Order in HW is switched for the wheel
|
||||||
|
s32 delta_wheel_y;
|
||||||
|
s32 delta_wheel_x;
|
||||||
|
MouseButton button;
|
||||||
|
MouseAttribute attribute;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
|
||||||
|
|
||||||
|
/// Converts a NpadIdType to an array index.
|
||||||
|
constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
|
||||||
|
switch (npad_id_type) {
|
||||||
|
case NpadIdType::Player1:
|
||||||
|
return 0;
|
||||||
|
case NpadIdType::Player2:
|
||||||
|
return 1;
|
||||||
|
case NpadIdType::Player3:
|
||||||
|
return 2;
|
||||||
|
case NpadIdType::Player4:
|
||||||
|
return 3;
|
||||||
|
case NpadIdType::Player5:
|
||||||
|
return 4;
|
||||||
|
case NpadIdType::Player6:
|
||||||
|
return 5;
|
||||||
|
case NpadIdType::Player7:
|
||||||
|
return 6;
|
||||||
|
case NpadIdType::Player8:
|
||||||
|
return 7;
|
||||||
|
case NpadIdType::Handheld:
|
||||||
|
return 8;
|
||||||
|
case NpadIdType::Other:
|
||||||
|
return 9;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an array index to a NpadIdType
|
||||||
|
constexpr NpadIdType IndexToNpadIdType(size_t index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return NpadIdType::Player1;
|
||||||
|
case 1:
|
||||||
|
return NpadIdType::Player2;
|
||||||
|
case 2:
|
||||||
|
return NpadIdType::Player3;
|
||||||
|
case 3:
|
||||||
|
return NpadIdType::Player4;
|
||||||
|
case 4:
|
||||||
|
return NpadIdType::Player5;
|
||||||
|
case 5:
|
||||||
|
return NpadIdType::Player6;
|
||||||
|
case 6:
|
||||||
|
return NpadIdType::Player7;
|
||||||
|
case 7:
|
||||||
|
return NpadIdType::Player8;
|
||||||
|
case 8:
|
||||||
|
return NpadIdType::Handheld;
|
||||||
|
case 9:
|
||||||
|
return NpadIdType::Other;
|
||||||
|
default:
|
||||||
|
return NpadIdType::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,383 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "common/input.h"
|
||||||
|
#include "core/hid/input_converter.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None};
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Analog:
|
||||||
|
case Common::Input::InputType::Trigger: {
|
||||||
|
const auto value = TransformToTrigger(callback).analog.value;
|
||||||
|
battery = Common::Input::BatteryLevel::Empty;
|
||||||
|
if (value > 0.2f) {
|
||||||
|
battery = Common::Input::BatteryLevel::Critical;
|
||||||
|
}
|
||||||
|
if (value > 0.4f) {
|
||||||
|
battery = Common::Input::BatteryLevel::Low;
|
||||||
|
}
|
||||||
|
if (value > 0.6f) {
|
||||||
|
battery = Common::Input::BatteryLevel::Medium;
|
||||||
|
}
|
||||||
|
if (value > 0.8f) {
|
||||||
|
battery = Common::Input::BatteryLevel::Full;
|
||||||
|
}
|
||||||
|
if (value >= 1.0f) {
|
||||||
|
battery = Common::Input::BatteryLevel::Charging;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Common::Input::InputType::Button:
|
||||||
|
battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging
|
||||||
|
: Common::Input::BatteryLevel::Critical;
|
||||||
|
break;
|
||||||
|
case Common::Input::InputType::Battery:
|
||||||
|
battery = callback.battery_status;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return battery;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::ButtonStatus status{};
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Analog:
|
||||||
|
case Common::Input::InputType::Trigger:
|
||||||
|
status.value = TransformToTrigger(callback).pressed.value;
|
||||||
|
break;
|
||||||
|
case Common::Input::InputType::Button:
|
||||||
|
status = callback.button_status;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.inverted) {
|
||||||
|
status.value = !status.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::MotionStatus status{};
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Button: {
|
||||||
|
Common::Input::AnalogProperties properties{
|
||||||
|
.deadzone = 0.0f,
|
||||||
|
.range = 1.0f,
|
||||||
|
.offset = 0.0f,
|
||||||
|
};
|
||||||
|
status.delta_timestamp = 5000;
|
||||||
|
status.force_update = true;
|
||||||
|
status.accel.x = {
|
||||||
|
.value = 0.0f,
|
||||||
|
.raw_value = 0.0f,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
status.accel.y = {
|
||||||
|
.value = 0.0f,
|
||||||
|
.raw_value = 0.0f,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
status.accel.z = {
|
||||||
|
.value = 0.0f,
|
||||||
|
.raw_value = -1.0f,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
status.gyro.x = {
|
||||||
|
.value = 0.0f,
|
||||||
|
.raw_value = 0.0f,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
status.gyro.y = {
|
||||||
|
.value = 0.0f,
|
||||||
|
.raw_value = 0.0f,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
status.gyro.z = {
|
||||||
|
.value = 0.0f,
|
||||||
|
.raw_value = 0.0f,
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
if (TransformToButton(callback).value) {
|
||||||
|
std::random_device device;
|
||||||
|
std::mt19937 gen(device());
|
||||||
|
std::uniform_int_distribution<s16> distribution(-1000, 1000);
|
||||||
|
status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
|
||||||
|
status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
|
||||||
|
status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
|
||||||
|
status.gyro.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
|
||||||
|
status.gyro.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
|
||||||
|
status.gyro.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Common::Input::InputType::Motion:
|
||||||
|
status = callback.motion_status;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SanitizeAnalog(status.accel.x, false);
|
||||||
|
SanitizeAnalog(status.accel.y, false);
|
||||||
|
SanitizeAnalog(status.accel.z, false);
|
||||||
|
SanitizeAnalog(status.gyro.x, false);
|
||||||
|
SanitizeAnalog(status.gyro.y, false);
|
||||||
|
SanitizeAnalog(status.gyro.z, false);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::StickStatus status{};
|
||||||
|
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Stick:
|
||||||
|
status = callback.stick_status;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SanitizeStick(status.x, status.y, true);
|
||||||
|
const auto& properties_x = status.x.properties;
|
||||||
|
const auto& properties_y = status.y.properties;
|
||||||
|
const float x = status.x.value;
|
||||||
|
const float y = status.y.value;
|
||||||
|
|
||||||
|
// Set directional buttons
|
||||||
|
status.right = x > properties_x.threshold;
|
||||||
|
status.left = x < -properties_x.threshold;
|
||||||
|
status.up = y > properties_y.threshold;
|
||||||
|
status.down = y < -properties_y.threshold;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::TouchStatus status{};
|
||||||
|
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Touch:
|
||||||
|
status = callback.touch_status;
|
||||||
|
break;
|
||||||
|
case Common::Input::InputType::Stick:
|
||||||
|
status.x = callback.stick_status.x;
|
||||||
|
status.y = callback.stick_status.y;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SanitizeAnalog(status.x, true);
|
||||||
|
SanitizeAnalog(status.y, true);
|
||||||
|
float& x = status.x.value;
|
||||||
|
float& y = status.y.value;
|
||||||
|
|
||||||
|
// Adjust if value is inverted
|
||||||
|
x = status.x.properties.inverted ? 1.0f + x : x;
|
||||||
|
y = status.y.properties.inverted ? 1.0f + y : y;
|
||||||
|
|
||||||
|
// clamp value
|
||||||
|
x = std::clamp(x, 0.0f, 1.0f);
|
||||||
|
y = std::clamp(y, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
if (status.pressed.inverted) {
|
||||||
|
status.pressed.value = !status.pressed.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::TriggerStatus status{};
|
||||||
|
float& raw_value = status.analog.raw_value;
|
||||||
|
bool calculate_button_value = true;
|
||||||
|
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Analog:
|
||||||
|
status.analog.properties = callback.analog_status.properties;
|
||||||
|
raw_value = callback.analog_status.raw_value;
|
||||||
|
break;
|
||||||
|
case Common::Input::InputType::Button:
|
||||||
|
status.analog.properties.range = 1.0f;
|
||||||
|
status.analog.properties.inverted = callback.button_status.inverted;
|
||||||
|
raw_value = callback.button_status.value ? 1.0f : 0.0f;
|
||||||
|
break;
|
||||||
|
case Common::Input::InputType::Trigger:
|
||||||
|
status = callback.trigger_status;
|
||||||
|
calculate_button_value = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SanitizeAnalog(status.analog, true);
|
||||||
|
const auto& properties = status.analog.properties;
|
||||||
|
float& value = status.analog.value;
|
||||||
|
|
||||||
|
// Set button status
|
||||||
|
if (calculate_button_value) {
|
||||||
|
status.pressed.value = value > properties.threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust if value is inverted
|
||||||
|
value = properties.inverted ? 1.0f + value : value;
|
||||||
|
|
||||||
|
// clamp value
|
||||||
|
value = std::clamp(value, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) {
|
||||||
|
Common::Input::AnalogStatus status{};
|
||||||
|
|
||||||
|
switch (callback.type) {
|
||||||
|
case Common::Input::InputType::Analog:
|
||||||
|
status.properties = callback.analog_status.properties;
|
||||||
|
status.raw_value = callback.analog_status.raw_value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SanitizeAnalog(status, false);
|
||||||
|
|
||||||
|
// Adjust if value is inverted
|
||||||
|
status.value = status.properties.inverted ? -status.value : status.value;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
|
||||||
|
const auto& properties = analog.properties;
|
||||||
|
float& raw_value = analog.raw_value;
|
||||||
|
float& value = analog.value;
|
||||||
|
|
||||||
|
if (!std::isnormal(raw_value)) {
|
||||||
|
raw_value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply center offset
|
||||||
|
raw_value -= properties.offset;
|
||||||
|
|
||||||
|
// Set initial values to be formated
|
||||||
|
value = raw_value;
|
||||||
|
|
||||||
|
// Calculate vector size
|
||||||
|
const float r = std::abs(value);
|
||||||
|
|
||||||
|
// Return zero if value is smaller than the deadzone
|
||||||
|
if (r <= properties.deadzone || properties.deadzone == 1.0f) {
|
||||||
|
analog.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust range of value
|
||||||
|
const float deadzone_factor =
|
||||||
|
1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
|
||||||
|
value = value * deadzone_factor / properties.range;
|
||||||
|
|
||||||
|
// Invert direction if needed
|
||||||
|
if (properties.inverted) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp value
|
||||||
|
if (clamp_value) {
|
||||||
|
value = std::clamp(value, -1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
|
||||||
|
bool clamp_value) {
|
||||||
|
const auto& properties_x = analog_x.properties;
|
||||||
|
const auto& properties_y = analog_y.properties;
|
||||||
|
float& raw_x = analog_x.raw_value;
|
||||||
|
float& raw_y = analog_y.raw_value;
|
||||||
|
float& x = analog_x.value;
|
||||||
|
float& y = analog_y.value;
|
||||||
|
|
||||||
|
if (!std::isnormal(raw_x)) {
|
||||||
|
raw_x = 0;
|
||||||
|
}
|
||||||
|
if (!std::isnormal(raw_y)) {
|
||||||
|
raw_y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply center offset
|
||||||
|
raw_x += properties_x.offset;
|
||||||
|
raw_y += properties_y.offset;
|
||||||
|
|
||||||
|
// Apply X scale correction from offset
|
||||||
|
if (std::abs(properties_x.offset) < 0.5f) {
|
||||||
|
if (raw_x > 0) {
|
||||||
|
raw_x /= 1 + properties_x.offset;
|
||||||
|
} else {
|
||||||
|
raw_x /= 1 - properties_x.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Y scale correction from offset
|
||||||
|
if (std::abs(properties_y.offset) < 0.5f) {
|
||||||
|
if (raw_y > 0) {
|
||||||
|
raw_y /= 1 + properties_y.offset;
|
||||||
|
} else {
|
||||||
|
raw_y /= 1 - properties_y.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert direction if needed
|
||||||
|
raw_x = properties_x.inverted ? -raw_x : raw_x;
|
||||||
|
raw_y = properties_y.inverted ? -raw_y : raw_y;
|
||||||
|
|
||||||
|
// Set initial values to be formated
|
||||||
|
x = raw_x;
|
||||||
|
y = raw_y;
|
||||||
|
|
||||||
|
// Calculate vector size
|
||||||
|
float r = x * x + y * y;
|
||||||
|
r = std::sqrt(r);
|
||||||
|
|
||||||
|
// TODO(German77): Use deadzone and range of both axis
|
||||||
|
|
||||||
|
// Return zero if values are smaller than the deadzone
|
||||||
|
if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust range of joystick
|
||||||
|
const float deadzone_factor =
|
||||||
|
1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
|
||||||
|
x = x * deadzone_factor / properties_x.range;
|
||||||
|
y = y * deadzone_factor / properties_x.range;
|
||||||
|
r = r * deadzone_factor / properties_x.range;
|
||||||
|
|
||||||
|
// Normalize joystick
|
||||||
|
if (clamp_value && r > 1.0f) {
|
||||||
|
x /= r;
|
||||||
|
y /= r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Common::Input {
|
||||||
|
struct CallbackStatus;
|
||||||
|
enum class BatteryLevel : u32;
|
||||||
|
using BatteryStatus = BatteryLevel;
|
||||||
|
struct AnalogStatus;
|
||||||
|
struct ButtonStatus;
|
||||||
|
struct MotionStatus;
|
||||||
|
struct StickStatus;
|
||||||
|
struct TouchStatus;
|
||||||
|
struct TriggerStatus;
|
||||||
|
}; // namespace Common::Input
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid battery status.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Analog, Battery, Trigger.
|
||||||
|
* @return A valid BatteryStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid button status. Applies invert properties to the output.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Analog, Button, Trigger.
|
||||||
|
* @return A valid TouchStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid motion status.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Motion.
|
||||||
|
* @return A valid TouchStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert
|
||||||
|
* properties to the output.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Stick.
|
||||||
|
* @return A valid StickStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid touch status.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Touch.
|
||||||
|
* @return A valid TouchStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid trigger status. Applies offset, deadzone, range and
|
||||||
|
* invert properties to the output. Button status uses the threshold property if necessary.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Analog, Button, Trigger.
|
||||||
|
* @return A valid TriggerStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw input data into a valid analog status. Applies offset, deadzone, range and
|
||||||
|
* invert properties to the output.
|
||||||
|
*
|
||||||
|
* @param callback Supported callbacks: Analog.
|
||||||
|
* @return A valid AnalogStatus object.
|
||||||
|
*/
|
||||||
|
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw analog data into a valid analog value
|
||||||
|
* @param analog An analog object containing raw data and properties
|
||||||
|
* @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
|
||||||
|
*/
|
||||||
|
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts raw stick data into a valid stick value
|
||||||
|
* @param analog_x raw analog data and properties for the x-axis
|
||||||
|
* @param analog_y raw analog data and properties for the y-axis
|
||||||
|
* @param clamp_value bool that determines if the value needs to be clamped into the unit circle.
|
||||||
|
*/
|
||||||
|
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
|
||||||
|
bool clamp_value);
|
||||||
|
|
||||||
|
} // namespace Core::HID
|
|
@ -3,7 +3,8 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/frontend/input_interpreter.h"
|
#include "core/hid/hid_types.h"
|
||||||
|
#include "core/hid/input_interpreter.h"
|
||||||
#include "core/hle/service/hid/controllers/npad.h"
|
#include "core/hle/service/hid/controllers/npad.h"
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hle/service/hid/hid.h"
|
||||||
#include "core/hle/service/sm/sm.h"
|
#include "core/hle/service/sm/sm.h"
|
||||||
|
@ -19,7 +20,7 @@ InputInterpreter::InputInterpreter(Core::System& system)
|
||||||
InputInterpreter::~InputInterpreter() = default;
|
InputInterpreter::~InputInterpreter() = default;
|
||||||
|
|
||||||
void InputInterpreter::PollInput() {
|
void InputInterpreter::PollInput() {
|
||||||
const u32 button_state = npad.GetAndResetPressState();
|
const auto button_state = npad.GetAndResetPressState();
|
||||||
|
|
||||||
previous_index = current_index;
|
previous_index = current_index;
|
||||||
current_index = (current_index + 1) % button_states.size();
|
current_index = (current_index + 1) % button_states.size();
|
||||||
|
@ -31,32 +32,30 @@ void InputInterpreter::ResetButtonStates() {
|
||||||
previous_index = 0;
|
previous_index = 0;
|
||||||
current_index = 0;
|
current_index = 0;
|
||||||
|
|
||||||
button_states[0] = 0xFFFFFFFF;
|
button_states[0] = Core::HID::NpadButton::All;
|
||||||
|
|
||||||
for (std::size_t i = 1; i < button_states.size(); ++i) {
|
for (std::size_t i = 1; i < button_states.size(); ++i) {
|
||||||
button_states[i] = 0;
|
button_states[i] = Core::HID::NpadButton::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputInterpreter::IsButtonPressed(HIDButton button) const {
|
bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const {
|
||||||
return (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
|
return True(button_states[current_index] & button);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
|
bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const {
|
||||||
const bool current_press =
|
const bool current_press = True(button_states[current_index] & button);
|
||||||
(button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
|
const bool previous_press = True(button_states[previous_index] & button);
|
||||||
const bool previous_press =
|
|
||||||
(button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
|
|
||||||
|
|
||||||
return current_press && !previous_press;
|
return current_press && !previous_press;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputInterpreter::IsButtonHeld(HIDButton button) const {
|
bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const {
|
||||||
u32 held_buttons{button_states[0]};
|
Core::HID::NpadButton held_buttons{button_states[0]};
|
||||||
|
|
||||||
for (std::size_t i = 1; i < button_states.size(); ++i) {
|
for (std::size_t i = 1; i < button_states.size(); ++i) {
|
||||||
held_buttons &= button_states[i];
|
held_buttons &= button_states[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (held_buttons & (1U << static_cast<u8>(button))) != 0;
|
return True(held_buttons & button);
|
||||||
}
|
}
|
|
@ -12,46 +12,14 @@ namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
enum class NpadButton : u64;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Service::HID {
|
namespace Service::HID {
|
||||||
class Controller_NPad;
|
class Controller_NPad;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class HIDButton : u8 {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
LStick,
|
|
||||||
RStick,
|
|
||||||
L,
|
|
||||||
R,
|
|
||||||
ZL,
|
|
||||||
ZR,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
|
|
||||||
DLeft,
|
|
||||||
DUp,
|
|
||||||
DRight,
|
|
||||||
DDown,
|
|
||||||
|
|
||||||
LStickLeft,
|
|
||||||
LStickUp,
|
|
||||||
LStickRight,
|
|
||||||
LStickDown,
|
|
||||||
|
|
||||||
RStickLeft,
|
|
||||||
RStickUp,
|
|
||||||
RStickRight,
|
|
||||||
RStickDown,
|
|
||||||
|
|
||||||
LeftSL,
|
|
||||||
LeftSR,
|
|
||||||
|
|
||||||
RightSL,
|
|
||||||
RightSR,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InputInterpreter class interfaces with HID to retrieve button press states.
|
* The InputInterpreter class interfaces with HID to retrieve button press states.
|
||||||
* Input is intended to be polled every 50ms so that a button is considered to be
|
* Input is intended to be polled every 50ms so that a button is considered to be
|
||||||
|
@ -76,7 +44,7 @@ public:
|
||||||
*
|
*
|
||||||
* @returns True when the button is pressed.
|
* @returns True when the button is pressed.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool IsButtonPressed(HIDButton button) const;
|
[[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether any of the buttons in the parameter list is pressed.
|
* Checks whether any of the buttons in the parameter list is pressed.
|
||||||
|
@ -85,7 +53,7 @@ public:
|
||||||
*
|
*
|
||||||
* @returns True when at least one of the buttons is pressed.
|
* @returns True when at least one of the buttons is pressed.
|
||||||
*/
|
*/
|
||||||
template <HIDButton... T>
|
template <Core::HID::NpadButton... T>
|
||||||
[[nodiscard]] bool IsAnyButtonPressed() {
|
[[nodiscard]] bool IsAnyButtonPressed() {
|
||||||
return (IsButtonPressed(T) || ...);
|
return (IsButtonPressed(T) || ...);
|
||||||
}
|
}
|
||||||
|
@ -98,7 +66,7 @@ public:
|
||||||
*
|
*
|
||||||
* @returns True when the button is pressed once.
|
* @returns True when the button is pressed once.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
|
[[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether any of the buttons in the parameter list is pressed once.
|
* Checks whether any of the buttons in the parameter list is pressed once.
|
||||||
|
@ -107,7 +75,7 @@ public:
|
||||||
*
|
*
|
||||||
* @returns True when at least one of the buttons is pressed once.
|
* @returns True when at least one of the buttons is pressed once.
|
||||||
*/
|
*/
|
||||||
template <HIDButton... T>
|
template <Core::HID::NpadButton... T>
|
||||||
[[nodiscard]] bool IsAnyButtonPressedOnce() const {
|
[[nodiscard]] bool IsAnyButtonPressedOnce() const {
|
||||||
return (IsButtonPressedOnce(T) || ...);
|
return (IsButtonPressedOnce(T) || ...);
|
||||||
}
|
}
|
||||||
|
@ -119,7 +87,7 @@ public:
|
||||||
*
|
*
|
||||||
* @returns True when the button is held down.
|
* @returns True when the button is held down.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool IsButtonHeld(HIDButton button) const;
|
[[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether any of the buttons in the parameter list is held down.
|
* Checks whether any of the buttons in the parameter list is held down.
|
||||||
|
@ -128,7 +96,7 @@ public:
|
||||||
*
|
*
|
||||||
* @returns True when at least one of the buttons is held down.
|
* @returns True when at least one of the buttons is held down.
|
||||||
*/
|
*/
|
||||||
template <HIDButton... T>
|
template <Core::HID::NpadButton... T>
|
||||||
[[nodiscard]] bool IsAnyButtonHeld() const {
|
[[nodiscard]] bool IsAnyButtonHeld() const {
|
||||||
return (IsButtonHeld(T) || ...);
|
return (IsButtonHeld(T) || ...);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +105,7 @@ private:
|
||||||
Service::HID::Controller_NPad& npad;
|
Service::HID::Controller_NPad& npad;
|
||||||
|
|
||||||
/// Stores 9 consecutive button states polled from HID.
|
/// Stores 9 consecutive button states polled from HID.
|
||||||
std::array<u32, 9> button_states{};
|
std::array<Core::HID::NpadButton, 9> button_states{};
|
||||||
|
|
||||||
std::size_t previous_index{};
|
std::size_t previous_index{};
|
||||||
std::size_t current_index{};
|
std::size_t current_index{};
|
|
@ -2,13 +2,21 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
#include "input_common/motion_input.h"
|
#include "core/hid/motion_input.h"
|
||||||
|
|
||||||
namespace InputCommon {
|
namespace Core::HID {
|
||||||
|
|
||||||
MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
|
MotionInput::MotionInput() {
|
||||||
|
// Initialize PID constants with default values
|
||||||
|
SetPID(0.3f, 0.005f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
|
||||||
|
kp = new_kp;
|
||||||
|
ki = new_ki;
|
||||||
|
kd = new_kd;
|
||||||
|
}
|
||||||
|
|
||||||
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
|
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
|
||||||
accel = acceleration;
|
accel = acceleration;
|
||||||
|
@ -65,6 +73,8 @@ void MotionInput::UpdateRotation(u64 elapsed_time) {
|
||||||
rotations += gyro * sample_period;
|
rotations += gyro * sample_period;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
|
||||||
|
// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
|
||||||
void MotionInput::UpdateOrientation(u64 elapsed_time) {
|
void MotionInput::UpdateOrientation(u64 elapsed_time) {
|
||||||
if (!IsCalibrated(0.1f)) {
|
if (!IsCalibrated(0.1f)) {
|
||||||
ResetOrientation();
|
ResetOrientation();
|
||||||
|
@ -190,43 +200,6 @@ Common::Vec3f MotionInput::GetRotations() const {
|
||||||
return rotations;
|
return rotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
Input::MotionStatus MotionInput::GetMotion() const {
|
|
||||||
const Common::Vec3f gyroscope = GetGyroscope();
|
|
||||||
const Common::Vec3f accelerometer = GetAcceleration();
|
|
||||||
const Common::Vec3f rotation = GetRotations();
|
|
||||||
const std::array<Common::Vec3f, 3> orientation = GetOrientation();
|
|
||||||
const Common::Quaternion<f32> quaternion = GetQuaternion();
|
|
||||||
return {accelerometer, gyroscope, rotation, orientation, quaternion};
|
|
||||||
}
|
|
||||||
|
|
||||||
Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
|
|
||||||
std::random_device device;
|
|
||||||
std::mt19937 gen(device());
|
|
||||||
std::uniform_int_distribution<s16> distribution(-1000, 1000);
|
|
||||||
const Common::Vec3f gyroscope{
|
|
||||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
|
||||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
|
||||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
|
||||||
};
|
|
||||||
const Common::Vec3f accelerometer{
|
|
||||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
|
||||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
|
||||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
|
||||||
};
|
|
||||||
constexpr Common::Vec3f rotation;
|
|
||||||
constexpr std::array orientation{
|
|
||||||
Common::Vec3f{1.0f, 0.0f, 0.0f},
|
|
||||||
Common::Vec3f{0.0f, 1.0f, 0.0f},
|
|
||||||
Common::Vec3f{0.0f, 0.0f, 1.0f},
|
|
||||||
};
|
|
||||||
constexpr Common::Quaternion<f32> quaternion{
|
|
||||||
{0.0f, 0.0f, 0.0f},
|
|
||||||
1.0f,
|
|
||||||
};
|
|
||||||
return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation,
|
|
||||||
quaternion};
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionInput::ResetOrientation() {
|
void MotionInput::ResetOrientation() {
|
||||||
if (!reset_enabled || only_accelerometer) {
|
if (!reset_enabled || only_accelerometer) {
|
||||||
return;
|
return;
|
||||||
|
@ -304,4 +277,4 @@ void MotionInput::SetOrientationFromAccelerometer() {
|
||||||
quat = quat.Normalized();
|
quat = quat.Normalized();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace InputCommon
|
} // namespace Core::HID
|
|
@ -7,13 +7,12 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/quaternion.h"
|
#include "common/quaternion.h"
|
||||||
#include "common/vector_math.h"
|
#include "common/vector_math.h"
|
||||||
#include "core/frontend/input.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
namespace Core::HID {
|
||||||
|
|
||||||
class MotionInput {
|
class MotionInput {
|
||||||
public:
|
public:
|
||||||
explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
|
explicit MotionInput();
|
||||||
|
|
||||||
MotionInput(const MotionInput&) = default;
|
MotionInput(const MotionInput&) = default;
|
||||||
MotionInput& operator=(const MotionInput&) = default;
|
MotionInput& operator=(const MotionInput&) = default;
|
||||||
|
@ -21,6 +20,7 @@ public:
|
||||||
MotionInput(MotionInput&&) = default;
|
MotionInput(MotionInput&&) = default;
|
||||||
MotionInput& operator=(MotionInput&&) = default;
|
MotionInput& operator=(MotionInput&&) = default;
|
||||||
|
|
||||||
|
void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
|
||||||
void SetAcceleration(const Common::Vec3f& acceleration);
|
void SetAcceleration(const Common::Vec3f& acceleration);
|
||||||
void SetGyroscope(const Common::Vec3f& gyroscope);
|
void SetGyroscope(const Common::Vec3f& gyroscope);
|
||||||
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
|
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
|
||||||
|
@ -38,9 +38,6 @@ public:
|
||||||
[[nodiscard]] Common::Vec3f GetGyroscope() const;
|
[[nodiscard]] Common::Vec3f GetGyroscope() const;
|
||||||
[[nodiscard]] Common::Vec3f GetRotations() const;
|
[[nodiscard]] Common::Vec3f GetRotations() const;
|
||||||
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
|
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
|
||||||
[[nodiscard]] Input::MotionStatus GetMotion() const;
|
|
||||||
[[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
|
|
||||||
int gyro_magnitude) const;
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
|
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
|
||||||
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
|
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
|
||||||
|
@ -59,16 +56,32 @@ private:
|
||||||
Common::Vec3f integral_error;
|
Common::Vec3f integral_error;
|
||||||
Common::Vec3f derivative_error;
|
Common::Vec3f derivative_error;
|
||||||
|
|
||||||
|
// Quaternion containing the device orientation
|
||||||
Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
|
Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
|
||||||
|
|
||||||
|
// Number of full rotations in each axis
|
||||||
Common::Vec3f rotations;
|
Common::Vec3f rotations;
|
||||||
|
|
||||||
|
// Acceleration vector measurement in G force
|
||||||
Common::Vec3f accel;
|
Common::Vec3f accel;
|
||||||
|
|
||||||
|
// Gyroscope vector measurement in radians/s.
|
||||||
Common::Vec3f gyro;
|
Common::Vec3f gyro;
|
||||||
|
|
||||||
|
// Vector to be substracted from gyro measurements
|
||||||
Common::Vec3f gyro_drift;
|
Common::Vec3f gyro_drift;
|
||||||
|
|
||||||
|
// Minimum gyro amplitude to detect if the device is moving
|
||||||
f32 gyro_threshold = 0.0f;
|
f32 gyro_threshold = 0.0f;
|
||||||
|
|
||||||
|
// Number of invalid sequential data
|
||||||
u32 reset_counter = 0;
|
u32 reset_counter = 0;
|
||||||
|
|
||||||
|
// If the provided data is invalid the device will be autocalibrated
|
||||||
bool reset_enabled = true;
|
bool reset_enabled = true;
|
||||||
|
|
||||||
|
// Use accelerometer values to calculate position
|
||||||
bool only_accelerometer = true;
|
bool only_accelerometer = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace InputCommon
|
} // namespace Core::HID
|
|
@ -9,6 +9,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hardware_properties.h"
|
#include "core/hardware_properties.h"
|
||||||
#include "core/hle/kernel/init/init_slab_setup.h"
|
#include "core/hle/kernel/init/init_slab_setup.h"
|
||||||
|
#include "core/hle/kernel/k_code_memory.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_memory_layout.h"
|
#include "core/hle/kernel/k_memory_layout.h"
|
||||||
#include "core/hle/kernel/k_memory_manager.h"
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
|
@ -32,6 +33,7 @@ namespace Kernel::Init {
|
||||||
HANDLER(KPort, (SLAB_COUNT(KPort)), ##__VA_ARGS__) \
|
HANDLER(KPort, (SLAB_COUNT(KPort)), ##__VA_ARGS__) \
|
||||||
HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ##__VA_ARGS__) \
|
HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ##__VA_ARGS__) \
|
||||||
HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__) \
|
HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__) \
|
||||||
|
HANDLER(KCodeMemory, (SLAB_COUNT(KCodeMemory)), ##__VA_ARGS__) \
|
||||||
HANDLER(KSession, (SLAB_COUNT(KSession)), ##__VA_ARGS__) \
|
HANDLER(KSession, (SLAB_COUNT(KSession)), ##__VA_ARGS__) \
|
||||||
HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__)
|
HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "core/hle/kernel/k_scheduler.h"
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
#include "core/hle/kernel/k_thread_queue.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/svc_results.h"
|
#include "core/hle/kernel/svc_results.h"
|
||||||
#include "core/hle/kernel/time_manager.h"
|
#include "core/hle/kernel/time_manager.h"
|
||||||
|
@ -28,7 +29,7 @@ bool ReadFromUser(Core::System& system, s32* out, VAddr address) {
|
||||||
|
|
||||||
bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) {
|
bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) {
|
||||||
auto& monitor = system.Monitor();
|
auto& monitor = system.Monitor();
|
||||||
const auto current_core = system.CurrentCoreIndex();
|
const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
|
||||||
|
|
||||||
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
||||||
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
||||||
|
@ -58,7 +59,7 @@ bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 valu
|
||||||
|
|
||||||
bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) {
|
bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) {
|
||||||
auto& monitor = system.Monitor();
|
auto& monitor = system.Monitor();
|
||||||
const auto current_core = system.CurrentCoreIndex();
|
const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
|
||||||
|
|
||||||
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
||||||
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
||||||
|
@ -85,6 +86,27 @@ bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ThreadQueueImplForKAddressArbiter final : public KThreadQueue {
|
||||||
|
public:
|
||||||
|
explicit ThreadQueueImplForKAddressArbiter(KernelCore& kernel_, KAddressArbiter::ThreadTree* t)
|
||||||
|
: KThreadQueue(kernel_), m_tree(t) {}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// If the thread is waiting on an address arbiter, remove it from the tree.
|
||||||
|
if (waiting_thread->IsWaitingForAddressArbiter()) {
|
||||||
|
m_tree->erase(m_tree->iterator_to(*waiting_thread));
|
||||||
|
waiting_thread->ClearAddressArbiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KAddressArbiter::ThreadTree* m_tree;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
|
ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
|
||||||
|
@ -96,14 +118,14 @@ ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
|
||||||
auto it = thread_tree.nfind_light({addr, -1});
|
auto it = thread_tree.nfind_light({addr, -1});
|
||||||
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
|
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
|
||||||
(it->GetAddressArbiterKey() == addr)) {
|
(it->GetAddressArbiterKey() == addr)) {
|
||||||
|
// End the thread's wait.
|
||||||
KThread* target_thread = std::addressof(*it);
|
KThread* target_thread = std::addressof(*it);
|
||||||
target_thread->SetSyncedObject(nullptr, ResultSuccess);
|
target_thread->EndWait(ResultSuccess);
|
||||||
|
|
||||||
ASSERT(target_thread->IsWaitingForAddressArbiter());
|
ASSERT(target_thread->IsWaitingForAddressArbiter());
|
||||||
target_thread->Wakeup();
|
target_thread->ClearAddressArbiter();
|
||||||
|
|
||||||
it = thread_tree.erase(it);
|
it = thread_tree.erase(it);
|
||||||
target_thread->ClearAddressArbiter();
|
|
||||||
++num_waiters;
|
++num_waiters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,14 +151,14 @@ ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32
|
||||||
auto it = thread_tree.nfind_light({addr, -1});
|
auto it = thread_tree.nfind_light({addr, -1});
|
||||||
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
|
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
|
||||||
(it->GetAddressArbiterKey() == addr)) {
|
(it->GetAddressArbiterKey() == addr)) {
|
||||||
|
// End the thread's wait.
|
||||||
KThread* target_thread = std::addressof(*it);
|
KThread* target_thread = std::addressof(*it);
|
||||||
target_thread->SetSyncedObject(nullptr, ResultSuccess);
|
target_thread->EndWait(ResultSuccess);
|
||||||
|
|
||||||
ASSERT(target_thread->IsWaitingForAddressArbiter());
|
ASSERT(target_thread->IsWaitingForAddressArbiter());
|
||||||
target_thread->Wakeup();
|
target_thread->ClearAddressArbiter();
|
||||||
|
|
||||||
it = thread_tree.erase(it);
|
it = thread_tree.erase(it);
|
||||||
target_thread->ClearAddressArbiter();
|
|
||||||
++num_waiters;
|
++num_waiters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,14 +219,14 @@ ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32
|
||||||
|
|
||||||
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
|
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
|
||||||
(it->GetAddressArbiterKey() == addr)) {
|
(it->GetAddressArbiterKey() == addr)) {
|
||||||
|
// End the thread's wait.
|
||||||
KThread* target_thread = std::addressof(*it);
|
KThread* target_thread = std::addressof(*it);
|
||||||
target_thread->SetSyncedObject(nullptr, ResultSuccess);
|
target_thread->EndWait(ResultSuccess);
|
||||||
|
|
||||||
ASSERT(target_thread->IsWaitingForAddressArbiter());
|
ASSERT(target_thread->IsWaitingForAddressArbiter());
|
||||||
target_thread->Wakeup();
|
target_thread->ClearAddressArbiter();
|
||||||
|
|
||||||
it = thread_tree.erase(it);
|
it = thread_tree.erase(it);
|
||||||
target_thread->ClearAddressArbiter();
|
|
||||||
++num_waiters;
|
++num_waiters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,6 +236,7 @@ ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32
|
||||||
ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) {
|
ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) {
|
||||||
// Prepare to wait.
|
// Prepare to wait.
|
||||||
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||||
|
ThreadQueueImplForKAddressArbiter wait_queue(kernel, std::addressof(thread_tree));
|
||||||
|
|
||||||
{
|
{
|
||||||
KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
|
KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
|
||||||
|
@ -224,9 +247,6 @@ ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement
|
||||||
return ResultTerminationRequested;
|
return ResultTerminationRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the synced object.
|
|
||||||
cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
|
|
||||||
|
|
||||||
// Read the value from userspace.
|
// Read the value from userspace.
|
||||||
s32 user_value{};
|
s32 user_value{};
|
||||||
bool succeeded{};
|
bool succeeded{};
|
||||||
|
@ -256,31 +276,20 @@ ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement
|
||||||
// Set the arbiter.
|
// Set the arbiter.
|
||||||
cur_thread->SetAddressArbiter(&thread_tree, addr);
|
cur_thread->SetAddressArbiter(&thread_tree, addr);
|
||||||
thread_tree.insert(*cur_thread);
|
thread_tree.insert(*cur_thread);
|
||||||
cur_thread->SetState(ThreadState::Waiting);
|
|
||||||
|
// Wait for the thread to finish.
|
||||||
|
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||||
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
|
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel the timer wait.
|
|
||||||
kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
|
|
||||||
|
|
||||||
// Remove from the address arbiter.
|
|
||||||
{
|
|
||||||
KScopedSchedulerLock sl(kernel);
|
|
||||||
|
|
||||||
if (cur_thread->IsWaitingForAddressArbiter()) {
|
|
||||||
thread_tree.erase(thread_tree.iterator_to(*cur_thread));
|
|
||||||
cur_thread->ClearAddressArbiter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the result.
|
// Get the result.
|
||||||
KSynchronizationObject* dummy{};
|
return cur_thread->GetWaitResult();
|
||||||
return cur_thread->GetWaitResult(&dummy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
|
ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
|
||||||
// Prepare to wait.
|
// Prepare to wait.
|
||||||
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||||
|
ThreadQueueImplForKAddressArbiter wait_queue(kernel, std::addressof(thread_tree));
|
||||||
|
|
||||||
{
|
{
|
||||||
KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
|
KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
|
||||||
|
@ -291,9 +300,6 @@ ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
|
||||||
return ResultTerminationRequested;
|
return ResultTerminationRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the synced object.
|
|
||||||
cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
|
|
||||||
|
|
||||||
// Read the value from userspace.
|
// Read the value from userspace.
|
||||||
s32 user_value{};
|
s32 user_value{};
|
||||||
if (!ReadFromUser(system, &user_value, addr)) {
|
if (!ReadFromUser(system, &user_value, addr)) {
|
||||||
|
@ -316,26 +322,14 @@ ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
|
||||||
// Set the arbiter.
|
// Set the arbiter.
|
||||||
cur_thread->SetAddressArbiter(&thread_tree, addr);
|
cur_thread->SetAddressArbiter(&thread_tree, addr);
|
||||||
thread_tree.insert(*cur_thread);
|
thread_tree.insert(*cur_thread);
|
||||||
cur_thread->SetState(ThreadState::Waiting);
|
|
||||||
|
// Wait for the thread to finish.
|
||||||
|
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||||
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
|
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel the timer wait.
|
|
||||||
kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
|
|
||||||
|
|
||||||
// Remove from the address arbiter.
|
|
||||||
{
|
|
||||||
KScopedSchedulerLock sl(kernel);
|
|
||||||
|
|
||||||
if (cur_thread->IsWaitingForAddressArbiter()) {
|
|
||||||
thread_tree.erase(thread_tree.iterator_to(*cur_thread));
|
|
||||||
cur_thread->ClearAddressArbiter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the result.
|
// Get the result.
|
||||||
KSynchronizationObject* dummy{};
|
return cur_thread->GetWaitResult();
|
||||||
return cur_thread->GetWaitResult(&dummy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -170,6 +170,10 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string& GetName() const {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RegisterWithKernel();
|
void RegisterWithKernel();
|
||||||
void UnregisterWithKernel();
|
void UnregisterWithKernel();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "core/hle/kernel/k_class_token.h"
|
#include "core/hle/kernel/k_class_token.h"
|
||||||
#include "core/hle/kernel/k_client_port.h"
|
#include "core/hle/kernel/k_client_port.h"
|
||||||
#include "core/hle/kernel/k_client_session.h"
|
#include "core/hle/kernel/k_client_session.h"
|
||||||
|
#include "core/hle/kernel/k_code_memory.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_port.h"
|
#include "core/hle/kernel/k_port.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
@ -48,7 +49,7 @@ static_assert(ClassToken<KWritableEvent> == 0b10001001'00000000);
|
||||||
static_assert(ClassToken<KTransferMemory> == 0b10010001'00000000);
|
static_assert(ClassToken<KTransferMemory> == 0b10010001'00000000);
|
||||||
// static_assert(ClassToken<KDeviceAddressSpace> == 0b01100001'00000000);
|
// static_assert(ClassToken<KDeviceAddressSpace> == 0b01100001'00000000);
|
||||||
// static_assert(ClassToken<KSessionRequest> == 0b10100001'00000000);
|
// static_assert(ClassToken<KSessionRequest> == 0b10100001'00000000);
|
||||||
// static_assert(ClassToken<KCodeMemory> == 0b11000001'00000000);
|
static_assert(ClassToken<KCodeMemory> == 0b11000001'00000000);
|
||||||
|
|
||||||
// Ensure that the token hierarchy is correct.
|
// Ensure that the token hierarchy is correct.
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ static_assert(ClassToken<KWritableEvent> == ((0b10001001 << 8) | ClassToken<KAut
|
||||||
static_assert(ClassToken<KTransferMemory> == ((0b10010001 << 8) | ClassToken<KAutoObject>));
|
static_assert(ClassToken<KTransferMemory> == ((0b10010001 << 8) | ClassToken<KAutoObject>));
|
||||||
// static_assert(ClassToken<KDeviceAddressSpace> == ((0b01100001 << 8) | ClassToken<KAutoObject>));
|
// static_assert(ClassToken<KDeviceAddressSpace> == ((0b01100001 << 8) | ClassToken<KAutoObject>));
|
||||||
// static_assert(ClassToken<KSessionRequest> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
// static_assert(ClassToken<KSessionRequest> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
||||||
// static_assert(ClassToken<KCodeMemory> == ((0b11000001 << 8) | ClassToken<KAutoObject>));
|
static_assert(ClassToken<KCodeMemory> == ((0b11000001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
|
||||||
// Ensure that the token hierarchy reflects the class hierarchy.
|
// Ensure that the token hierarchy reflects the class hierarchy.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/device_memory.h"
|
||||||
|
#include "core/hle/kernel/k_auto_object.h"
|
||||||
|
#include "core/hle/kernel/k_code_memory.h"
|
||||||
|
#include "core/hle/kernel/k_light_lock.h"
|
||||||
|
#include "core/hle/kernel/k_memory_block.h"
|
||||||
|
#include "core/hle/kernel/k_page_linked_list.h"
|
||||||
|
#include "core/hle/kernel/k_page_table.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
|
#include "core/hle/kernel/svc_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
KCodeMemory::KCodeMemory(KernelCore& kernel_)
|
||||||
|
: KAutoObjectWithSlabHeapAndContainer{kernel_}, m_lock(kernel_) {}
|
||||||
|
|
||||||
|
ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, size_t size) {
|
||||||
|
// Set members.
|
||||||
|
m_owner = kernel.CurrentProcess();
|
||||||
|
|
||||||
|
// Get the owner page table.
|
||||||
|
auto& page_table = m_owner->PageTable();
|
||||||
|
|
||||||
|
// Construct the page group.
|
||||||
|
KMemoryInfo kBlockInfo = page_table.QueryInfo(addr);
|
||||||
|
m_page_group = KPageLinkedList(kBlockInfo.GetAddress(), kBlockInfo.GetNumPages());
|
||||||
|
|
||||||
|
// Lock the memory.
|
||||||
|
R_TRY(page_table.LockForCodeMemory(addr, size))
|
||||||
|
|
||||||
|
// Clear the memory.
|
||||||
|
for (const auto& block : m_page_group.Nodes()) {
|
||||||
|
std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set remaining tracking members.
|
||||||
|
m_address = addr;
|
||||||
|
m_is_initialized = true;
|
||||||
|
m_is_owner_mapped = false;
|
||||||
|
m_is_mapped = false;
|
||||||
|
|
||||||
|
// We succeeded.
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KCodeMemory::Finalize() {
|
||||||
|
// Unlock.
|
||||||
|
if (!m_is_mapped && !m_is_owner_mapped) {
|
||||||
|
const size_t size = m_page_group.GetNumPages() * PageSize;
|
||||||
|
m_owner->PageTable().UnlockForCodeMemory(m_address, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode KCodeMemory::Map(VAddr address, size_t size) {
|
||||||
|
// Validate the size.
|
||||||
|
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||||
|
|
||||||
|
// Lock ourselves.
|
||||||
|
KScopedLightLock lk(m_lock);
|
||||||
|
|
||||||
|
// Ensure we're not already mapped.
|
||||||
|
R_UNLESS(!m_is_mapped, ResultInvalidState);
|
||||||
|
|
||||||
|
// Map the memory.
|
||||||
|
R_TRY(kernel.CurrentProcess()->PageTable().MapPages(
|
||||||
|
address, m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
|
||||||
|
|
||||||
|
// Mark ourselves as mapped.
|
||||||
|
m_is_mapped = true;
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode KCodeMemory::Unmap(VAddr address, size_t size) {
|
||||||
|
// Validate the size.
|
||||||
|
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||||
|
|
||||||
|
// Lock ourselves.
|
||||||
|
KScopedLightLock lk(m_lock);
|
||||||
|
|
||||||
|
// Unmap the memory.
|
||||||
|
R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, m_page_group,
|
||||||
|
KMemoryState::CodeOut));
|
||||||
|
|
||||||
|
// Mark ourselves as unmapped.
|
||||||
|
m_is_mapped = false;
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
|
||||||
|
// Validate the size.
|
||||||
|
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||||
|
|
||||||
|
// Lock ourselves.
|
||||||
|
KScopedLightLock lk(m_lock);
|
||||||
|
|
||||||
|
// Ensure we're not already mapped.
|
||||||
|
R_UNLESS(!m_is_owner_mapped, ResultInvalidState);
|
||||||
|
|
||||||
|
// Convert the memory permission.
|
||||||
|
KMemoryPermission k_perm{};
|
||||||
|
switch (perm) {
|
||||||
|
case Svc::MemoryPermission::Read:
|
||||||
|
k_perm = KMemoryPermission::UserRead;
|
||||||
|
break;
|
||||||
|
case Svc::MemoryPermission::ReadExecute:
|
||||||
|
k_perm = KMemoryPermission::UserReadExecute;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the memory.
|
||||||
|
R_TRY(
|
||||||
|
m_owner->PageTable().MapPages(address, m_page_group, KMemoryState::GeneratedCode, k_perm));
|
||||||
|
|
||||||
|
// Mark ourselves as mapped.
|
||||||
|
m_is_owner_mapped = true;
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
|
||||||
|
// Validate the size.
|
||||||
|
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||||
|
|
||||||
|
// Lock ourselves.
|
||||||
|
KScopedLightLock lk(m_lock);
|
||||||
|
|
||||||
|
// Unmap the memory.
|
||||||
|
R_TRY(m_owner->PageTable().UnmapPages(address, m_page_group, KMemoryState::GeneratedCode));
|
||||||
|
|
||||||
|
// Mark ourselves as unmapped.
|
||||||
|
m_is_owner_mapped = false;
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/device_memory.h"
|
||||||
|
#include "core/hle/kernel/k_auto_object.h"
|
||||||
|
#include "core/hle/kernel/k_light_lock.h"
|
||||||
|
#include "core/hle/kernel/k_page_linked_list.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
|
#include "core/hle/kernel/svc_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
enum class CodeMemoryOperation : u32 {
|
||||||
|
Map = 0,
|
||||||
|
MapToOwner = 1,
|
||||||
|
Unmap = 2,
|
||||||
|
UnmapFromOwner = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
class KCodeMemory final
|
||||||
|
: public KAutoObjectWithSlabHeapAndContainer<KCodeMemory, KAutoObjectWithList> {
|
||||||
|
KERNEL_AUTOOBJECT_TRAITS(KCodeMemory, KAutoObject);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KCodeMemory(KernelCore& kernel_);
|
||||||
|
|
||||||
|
ResultCode Initialize(Core::DeviceMemory& device_memory, VAddr address, size_t size);
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
|
ResultCode Map(VAddr address, size_t size);
|
||||||
|
ResultCode Unmap(VAddr address, size_t size);
|
||||||
|
ResultCode MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm);
|
||||||
|
ResultCode UnmapFromOwner(VAddr address, size_t size);
|
||||||
|
|
||||||
|
bool IsInitialized() const {
|
||||||
|
return m_is_initialized;
|
||||||
|
}
|
||||||
|
static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
|
||||||
|
|
||||||
|
KProcess* GetOwner() const {
|
||||||
|
return m_owner;
|
||||||
|
}
|
||||||
|
VAddr GetSourceAddress() const {
|
||||||
|
return m_address;
|
||||||
|
}
|
||||||
|
size_t GetSize() const {
|
||||||
|
return m_is_initialized ? m_page_group.GetNumPages() * PageSize : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KPageLinkedList m_page_group{};
|
||||||
|
KProcess* m_owner{};
|
||||||
|
VAddr m_address{};
|
||||||
|
KLightLock m_lock;
|
||||||
|
bool m_is_initialized{};
|
||||||
|
bool m_is_owner_mapped{};
|
||||||
|
bool m_is_mapped{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||||
#include "core/hle/kernel/k_synchronization_object.h"
|
#include "core/hle/kernel/k_synchronization_object.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
#include "core/hle/kernel/k_thread_queue.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/svc_common.h"
|
#include "core/hle/kernel/svc_common.h"
|
||||||
#include "core/hle/kernel/svc_results.h"
|
#include "core/hle/kernel/svc_results.h"
|
||||||
|
@ -33,7 +34,7 @@ bool WriteToUser(Core::System& system, VAddr address, const u32* p) {
|
||||||
bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero,
|
bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero,
|
||||||
u32 new_orr_mask) {
|
u32 new_orr_mask) {
|
||||||
auto& monitor = system.Monitor();
|
auto& monitor = system.Monitor();
|
||||||
const auto current_core = system.CurrentCoreIndex();
|
const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
|
||||||
|
|
||||||
// Load the value from the address.
|
// Load the value from the address.
|
||||||
const auto expected = monitor.ExclusiveRead32(current_core, address);
|
const auto expected = monitor.ExclusiveRead32(current_core, address);
|
||||||
|
@ -57,6 +58,48 @@ bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ThreadQueueImplForKConditionVariableWaitForAddress final : public KThreadQueue {
|
||||||
|
public:
|
||||||
|
explicit ThreadQueueImplForKConditionVariableWaitForAddress(KernelCore& kernel_)
|
||||||
|
: KThreadQueue(kernel_) {}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// Remove the thread as a waiter from its owner.
|
||||||
|
waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ThreadQueueImplForKConditionVariableWaitConditionVariable final : public KThreadQueue {
|
||||||
|
private:
|
||||||
|
KConditionVariable::ThreadTree* m_tree;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ThreadQueueImplForKConditionVariableWaitConditionVariable(
|
||||||
|
KernelCore& kernel_, KConditionVariable::ThreadTree* t)
|
||||||
|
: KThreadQueue(kernel_), m_tree(t) {}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// Remove the thread as a waiter from its owner.
|
||||||
|
if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
|
||||||
|
owner->RemoveWaiter(waiting_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the thread is waiting on a condvar, remove it from the tree.
|
||||||
|
if (waiting_thread->IsWaitingForConditionVariable()) {
|
||||||
|
m_tree->erase(m_tree->iterator_to(*waiting_thread));
|
||||||
|
waiting_thread->ClearConditionVariable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
KConditionVariable::KConditionVariable(Core::System& system_)
|
KConditionVariable::KConditionVariable(Core::System& system_)
|
||||||
|
@ -78,84 +121,77 @@ ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
|
||||||
|
|
||||||
// Determine the next tag.
|
// Determine the next tag.
|
||||||
u32 next_value{};
|
u32 next_value{};
|
||||||
if (next_owner_thread) {
|
if (next_owner_thread != nullptr) {
|
||||||
next_value = next_owner_thread->GetAddressKeyValue();
|
next_value = next_owner_thread->GetAddressKeyValue();
|
||||||
if (num_waiters > 1) {
|
if (num_waiters > 1) {
|
||||||
next_value |= Svc::HandleWaitMask;
|
next_value |= Svc::HandleWaitMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_owner_thread->SetSyncedObject(nullptr, ResultSuccess);
|
|
||||||
next_owner_thread->Wakeup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the value to userspace.
|
// Write the value to userspace.
|
||||||
if (!WriteToUser(system, addr, std::addressof(next_value))) {
|
ResultCode result{ResultSuccess};
|
||||||
if (next_owner_thread) {
|
if (WriteToUser(system, addr, std::addressof(next_value))) [[likely]] {
|
||||||
next_owner_thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory);
|
result = ResultSuccess;
|
||||||
|
} else {
|
||||||
|
result = ResultInvalidCurrentMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultInvalidCurrentMemory;
|
// Signal the next owner thread.
|
||||||
}
|
next_owner_thread->EndWait(result);
|
||||||
}
|
return result;
|
||||||
|
} else {
|
||||||
|
// Just write the value to userspace.
|
||||||
|
R_UNLESS(WriteToUser(system, addr, std::addressof(next_value)),
|
||||||
|
ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
|
ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
|
||||||
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||||
|
ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);
|
||||||
|
|
||||||
// Wait for the address.
|
// Wait for the address.
|
||||||
{
|
KThread* owner_thread{};
|
||||||
KScopedAutoObject<KThread> owner_thread;
|
|
||||||
ASSERT(owner_thread.IsNull());
|
|
||||||
{
|
{
|
||||||
KScopedSchedulerLock sl(kernel);
|
KScopedSchedulerLock sl(kernel);
|
||||||
cur_thread->SetSyncedObject(nullptr, ResultSuccess);
|
|
||||||
|
|
||||||
// Check if the thread should terminate.
|
// Check if the thread should terminate.
|
||||||
R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
|
R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
|
||||||
|
|
||||||
{
|
|
||||||
// Read the tag from userspace.
|
// Read the tag from userspace.
|
||||||
u32 test_tag{};
|
u32 test_tag{};
|
||||||
R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr),
|
R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr), ResultInvalidCurrentMemory);
|
||||||
ResultInvalidCurrentMemory);
|
|
||||||
|
|
||||||
// If the tag isn't the handle (with wait mask), we're done.
|
// If the tag isn't the handle (with wait mask), we're done.
|
||||||
R_UNLESS(test_tag == (handle | Svc::HandleWaitMask), ResultSuccess);
|
R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask));
|
||||||
|
|
||||||
// Get the lock owner thread.
|
// Get the lock owner thread.
|
||||||
owner_thread =
|
owner_thread = kernel.CurrentProcess()
|
||||||
kernel.CurrentProcess()->GetHandleTable().GetObjectWithoutPseudoHandle<KThread>(
|
->GetHandleTable()
|
||||||
handle);
|
.GetObjectWithoutPseudoHandle<KThread>(handle)
|
||||||
R_UNLESS(owner_thread.IsNotNull(), ResultInvalidHandle);
|
.ReleasePointerUnsafe();
|
||||||
|
R_UNLESS(owner_thread != nullptr, ResultInvalidHandle);
|
||||||
|
|
||||||
// Update the lock.
|
// Update the lock.
|
||||||
cur_thread->SetAddressKey(addr, value);
|
cur_thread->SetAddressKey(addr, value);
|
||||||
owner_thread->AddWaiter(cur_thread);
|
owner_thread->AddWaiter(cur_thread);
|
||||||
cur_thread->SetState(ThreadState::Waiting);
|
|
||||||
|
// Begin waiting.
|
||||||
|
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||||
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
|
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
|
||||||
cur_thread->SetMutexWaitAddressForDebugging(addr);
|
cur_thread->SetMutexWaitAddressForDebugging(addr);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ASSERT(owner_thread.IsNotNull());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the thread as a waiter from the lock owner.
|
// Close our reference to the owner thread, now that the wait is over.
|
||||||
{
|
owner_thread->Close();
|
||||||
KScopedSchedulerLock sl(kernel);
|
|
||||||
KThread* owner_thread = cur_thread->GetLockOwner();
|
|
||||||
if (owner_thread != nullptr) {
|
|
||||||
owner_thread->RemoveWaiter(cur_thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the wait result.
|
// Get the wait result.
|
||||||
KSynchronizationObject* dummy{};
|
return cur_thread->GetWaitResult();
|
||||||
return cur_thread->GetWaitResult(std::addressof(dummy));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KThread* KConditionVariable::SignalImpl(KThread* thread) {
|
void KConditionVariable::SignalImpl(KThread* thread) {
|
||||||
// Check pre-conditions.
|
// Check pre-conditions.
|
||||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||||
|
|
||||||
|
@ -169,18 +205,16 @@ KThread* KConditionVariable::SignalImpl(KThread* thread) {
|
||||||
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
||||||
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
||||||
can_access = true;
|
can_access = true;
|
||||||
if (can_access) {
|
if (can_access) [[likely]] {
|
||||||
UpdateLockAtomic(system, std::addressof(prev_tag), address, own_tag,
|
UpdateLockAtomic(system, std::addressof(prev_tag), address, own_tag,
|
||||||
Svc::HandleWaitMask);
|
Svc::HandleWaitMask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KThread* thread_to_close = nullptr;
|
if (can_access) [[likely]] {
|
||||||
if (can_access) {
|
|
||||||
if (prev_tag == Svc::InvalidHandle) {
|
if (prev_tag == Svc::InvalidHandle) {
|
||||||
// If nobody held the lock previously, we're all good.
|
// If nobody held the lock previously, we're all good.
|
||||||
thread->SetSyncedObject(nullptr, ResultSuccess);
|
thread->EndWait(ResultSuccess);
|
||||||
thread->Wakeup();
|
|
||||||
} else {
|
} else {
|
||||||
// Get the previous owner.
|
// Get the previous owner.
|
||||||
KThread* owner_thread = kernel.CurrentProcess()
|
KThread* owner_thread = kernel.CurrentProcess()
|
||||||
|
@ -189,33 +223,22 @@ KThread* KConditionVariable::SignalImpl(KThread* thread) {
|
||||||
static_cast<Handle>(prev_tag & ~Svc::HandleWaitMask))
|
static_cast<Handle>(prev_tag & ~Svc::HandleWaitMask))
|
||||||
.ReleasePointerUnsafe();
|
.ReleasePointerUnsafe();
|
||||||
|
|
||||||
if (owner_thread) {
|
if (owner_thread) [[likely]] {
|
||||||
// Add the thread as a waiter on the owner.
|
// Add the thread as a waiter on the owner.
|
||||||
owner_thread->AddWaiter(thread);
|
owner_thread->AddWaiter(thread);
|
||||||
thread_to_close = owner_thread;
|
owner_thread->Close();
|
||||||
} else {
|
} else {
|
||||||
// The lock was tagged with a thread that doesn't exist.
|
// The lock was tagged with a thread that doesn't exist.
|
||||||
thread->SetSyncedObject(nullptr, ResultInvalidState);
|
thread->EndWait(ResultInvalidState);
|
||||||
thread->Wakeup();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the address wasn't accessible, note so.
|
// If the address wasn't accessible, note so.
|
||||||
thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory);
|
thread->EndWait(ResultInvalidCurrentMemory);
|
||||||
thread->Wakeup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return thread_to_close;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KConditionVariable::Signal(u64 cv_key, s32 count) {
|
void KConditionVariable::Signal(u64 cv_key, s32 count) {
|
||||||
// Prepare for signaling.
|
|
||||||
constexpr int MaxThreads = 16;
|
|
||||||
|
|
||||||
KLinkedList<KThread> thread_list{kernel};
|
|
||||||
std::array<KThread*, MaxThreads> thread_array;
|
|
||||||
s32 num_to_close{};
|
|
||||||
|
|
||||||
// Perform signaling.
|
// Perform signaling.
|
||||||
s32 num_waiters{};
|
s32 num_waiters{};
|
||||||
{
|
{
|
||||||
|
@ -226,14 +249,7 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) {
|
||||||
(it->GetConditionVariableKey() == cv_key)) {
|
(it->GetConditionVariableKey() == cv_key)) {
|
||||||
KThread* target_thread = std::addressof(*it);
|
KThread* target_thread = std::addressof(*it);
|
||||||
|
|
||||||
if (KThread* thread = SignalImpl(target_thread); thread != nullptr) {
|
this->SignalImpl(target_thread);
|
||||||
if (num_to_close < MaxThreads) {
|
|
||||||
thread_array[num_to_close++] = thread;
|
|
||||||
} else {
|
|
||||||
thread_list.push_back(*thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it = thread_tree.erase(it);
|
it = thread_tree.erase(it);
|
||||||
target_thread->ClearConditionVariable();
|
target_thread->ClearConditionVariable();
|
||||||
++num_waiters;
|
++num_waiters;
|
||||||
|
@ -245,27 +261,16 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) {
|
||||||
WriteToUser(system, cv_key, std::addressof(has_waiter_flag));
|
WriteToUser(system, cv_key, std::addressof(has_waiter_flag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close threads in the array.
|
|
||||||
for (auto i = 0; i < num_to_close; ++i) {
|
|
||||||
thread_array[i]->Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close threads in the list.
|
|
||||||
for (auto it = thread_list.begin(); it != thread_list.end(); it = thread_list.erase(it)) {
|
|
||||||
(*it).Close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
|
ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
|
||||||
// Prepare to wait.
|
// Prepare to wait.
|
||||||
KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
KThread* cur_thread = GetCurrentThreadPointer(kernel);
|
||||||
|
ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(
|
||||||
|
kernel, std::addressof(thread_tree));
|
||||||
|
|
||||||
{
|
{
|
||||||
KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
|
KScopedSchedulerLockAndSleep slp(kernel, cur_thread, timeout);
|
||||||
|
|
||||||
// Set the synced object.
|
|
||||||
cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
|
|
||||||
|
|
||||||
// Check that the thread isn't terminating.
|
// Check that the thread isn't terminating.
|
||||||
if (cur_thread->IsTerminationRequested()) {
|
if (cur_thread->IsTerminationRequested()) {
|
||||||
|
@ -290,8 +295,7 @@ ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wake up the next owner.
|
// Wake up the next owner.
|
||||||
next_owner_thread->SetSyncedObject(nullptr, ResultSuccess);
|
next_owner_thread->EndWait(ResultSuccess);
|
||||||
next_owner_thread->Wakeup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to the cv key.
|
// Write to the cv key.
|
||||||
|
@ -308,40 +312,21 @@ ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If timeout is zero, time out.
|
||||||
|
R_UNLESS(timeout != 0, ResultTimedOut);
|
||||||
|
|
||||||
// Update condition variable tracking.
|
// Update condition variable tracking.
|
||||||
{
|
|
||||||
cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value);
|
cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value);
|
||||||
thread_tree.insert(*cur_thread);
|
thread_tree.insert(*cur_thread);
|
||||||
}
|
|
||||||
|
|
||||||
// If the timeout is non-zero, set the thread as waiting.
|
// Begin waiting.
|
||||||
if (timeout != 0) {
|
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||||
cur_thread->SetState(ThreadState::Waiting);
|
|
||||||
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
|
cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
|
||||||
cur_thread->SetMutexWaitAddressForDebugging(addr);
|
cur_thread->SetMutexWaitAddressForDebugging(addr);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel the timer wait.
|
// Get the wait result.
|
||||||
kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
|
return cur_thread->GetWaitResult();
|
||||||
|
|
||||||
// Remove from the condition variable.
|
|
||||||
{
|
|
||||||
KScopedSchedulerLock sl(kernel);
|
|
||||||
|
|
||||||
if (KThread* owner = cur_thread->GetLockOwner(); owner != nullptr) {
|
|
||||||
owner->RemoveWaiter(cur_thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur_thread->IsWaitingForConditionVariable()) {
|
|
||||||
thread_tree.erase(thread_tree.iterator_to(*cur_thread));
|
|
||||||
cur_thread->ClearConditionVariable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the result.
|
|
||||||
KSynchronizationObject* dummy{};
|
|
||||||
return cur_thread->GetWaitResult(std::addressof(dummy));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -34,7 +34,7 @@ public:
|
||||||
[[nodiscard]] ResultCode Wait(VAddr addr, u64 key, u32 value, s64 timeout);
|
[[nodiscard]] ResultCode Wait(VAddr addr, u64 key, u32 value, s64 timeout);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] KThread* SignalImpl(KThread* thread);
|
void SignalImpl(KThread* thread);
|
||||||
|
|
||||||
ThreadTree thread_tree;
|
ThreadTree thread_tree;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ ResultCode KHandleTable::Finalize() {
|
||||||
// Get the table and clear our record of it.
|
// Get the table and clear our record of it.
|
||||||
u16 saved_table_size = 0;
|
u16 saved_table_size = 0;
|
||||||
{
|
{
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
std::swap(m_table_size, saved_table_size);
|
std::swap(m_table_size, saved_table_size);
|
||||||
|
@ -43,6 +44,7 @@ bool KHandleTable::Remove(Handle handle) {
|
||||||
// Find the object and free the entry.
|
// Find the object and free the entry.
|
||||||
KAutoObject* obj = nullptr;
|
KAutoObject* obj = nullptr;
|
||||||
{
|
{
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
if (this->IsValidHandle(handle)) {
|
if (this->IsValidHandle(handle)) {
|
||||||
|
@ -62,6 +64,7 @@ bool KHandleTable::Remove(Handle handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
|
ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
// Never exceed our capacity.
|
// Never exceed our capacity.
|
||||||
|
@ -84,6 +87,7 @@ ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KHandleTable::Reserve(Handle* out_handle) {
|
ResultCode KHandleTable::Reserve(Handle* out_handle) {
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
// Never exceed our capacity.
|
// Never exceed our capacity.
|
||||||
|
@ -94,6 +98,7 @@ ResultCode KHandleTable::Reserve(Handle* out_handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void KHandleTable::Unreserve(Handle handle) {
|
void KHandleTable::Unreserve(Handle handle) {
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
// Unpack the handle.
|
// Unpack the handle.
|
||||||
|
@ -112,6 +117,7 @@ void KHandleTable::Unreserve(Handle handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void KHandleTable::Register(Handle handle, KAutoObject* obj, u16 type) {
|
void KHandleTable::Register(Handle handle, KAutoObject* obj, u16 type) {
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
// Unpack the handle.
|
// Unpack the handle.
|
||||||
|
|
|
@ -68,6 +68,7 @@ public:
|
||||||
template <typename T = KAutoObject>
|
template <typename T = KAutoObject>
|
||||||
KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const {
|
KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const {
|
||||||
// Lock and look up in table.
|
// Lock and look up in table.
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, KAutoObject>) {
|
if constexpr (std::is_same_v<T, KAutoObject>) {
|
||||||
|
@ -122,6 +123,7 @@ public:
|
||||||
size_t num_opened;
|
size_t num_opened;
|
||||||
{
|
{
|
||||||
// Lock the table.
|
// Lock the table.
|
||||||
|
KScopedDisableDispatch dd(kernel);
|
||||||
KScopedSpinLock lk(m_lock);
|
KScopedSpinLock lk(m_lock);
|
||||||
for (num_opened = 0; num_opened < num_handles; num_opened++) {
|
for (num_opened = 0; num_opened < num_handles; num_opened++) {
|
||||||
// Get the current handle.
|
// Get the current handle.
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_light_condition_variable.h"
|
||||||
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
|
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||||
|
#include "core/hle/kernel/k_thread_queue.h"
|
||||||
|
#include "core/hle/kernel/svc_results.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue {
|
||||||
|
public:
|
||||||
|
ThreadQueueImplForKLightConditionVariable(KernelCore& kernel_, KThread::WaiterList* wl,
|
||||||
|
bool term)
|
||||||
|
: KThreadQueue(kernel_), m_wait_list(wl), m_allow_terminating_thread(term) {}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// Only process waits if we're allowed to.
|
||||||
|
if (ResultTerminationRequested == wait_result && m_allow_terminating_thread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the thread from the waiting thread from the light condition variable.
|
||||||
|
m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KThread::WaiterList* m_wait_list;
|
||||||
|
bool m_allow_terminating_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void KLightConditionVariable::Wait(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
|
||||||
|
// Create thread queue.
|
||||||
|
KThread* owner = GetCurrentThreadPointer(kernel);
|
||||||
|
|
||||||
|
ThreadQueueImplForKLightConditionVariable wait_queue(kernel, std::addressof(wait_list),
|
||||||
|
allow_terminating_thread);
|
||||||
|
|
||||||
|
// Sleep the thread.
|
||||||
|
{
|
||||||
|
KScopedSchedulerLockAndSleep lk(kernel, owner, timeout);
|
||||||
|
|
||||||
|
if (!allow_terminating_thread && owner->IsTerminationRequested()) {
|
||||||
|
lk.CancelSleep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock->Unlock();
|
||||||
|
|
||||||
|
// Add the thread to the queue.
|
||||||
|
wait_list.push_back(*owner);
|
||||||
|
|
||||||
|
// Begin waiting.
|
||||||
|
owner->BeginWait(std::addressof(wait_queue));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-acquire the lock.
|
||||||
|
lock->Lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KLightConditionVariable::Broadcast() {
|
||||||
|
KScopedSchedulerLock lk(kernel);
|
||||||
|
|
||||||
|
// Signal all threads.
|
||||||
|
for (auto it = wait_list.begin(); it != wait_list.end(); it = wait_list.erase(it)) {
|
||||||
|
it->EndWait(ResultSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
|
@ -2,72 +2,24 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
|
||||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hle/kernel/k_scheduler.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
|
||||||
#include "core/hle/kernel/time_manager.h"
|
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
class KernelCore;
|
class KernelCore;
|
||||||
|
class KLightLock;
|
||||||
|
|
||||||
class KLightConditionVariable {
|
class KLightConditionVariable {
|
||||||
public:
|
public:
|
||||||
explicit KLightConditionVariable(KernelCore& kernel_) : kernel{kernel_} {}
|
explicit KLightConditionVariable(KernelCore& kernel_) : kernel{kernel_} {}
|
||||||
|
|
||||||
void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true) {
|
void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true);
|
||||||
WaitImpl(lock, timeout, allow_terminating_thread);
|
void Broadcast();
|
||||||
}
|
|
||||||
|
|
||||||
void Broadcast() {
|
|
||||||
KScopedSchedulerLock lk{kernel};
|
|
||||||
|
|
||||||
// Signal all threads.
|
|
||||||
for (auto& thread : wait_list) {
|
|
||||||
thread.SetState(ThreadState::Runnable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void WaitImpl(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
|
|
||||||
KThread* owner = GetCurrentThreadPointer(kernel);
|
|
||||||
|
|
||||||
// Sleep the thread.
|
|
||||||
{
|
|
||||||
KScopedSchedulerLockAndSleep lk{kernel, owner, timeout};
|
|
||||||
|
|
||||||
if (!allow_terminating_thread && owner->IsTerminationRequested()) {
|
|
||||||
lk.CancelSleep();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock->Unlock();
|
|
||||||
|
|
||||||
// Set the thread as waiting.
|
|
||||||
GetCurrentThread(kernel).SetState(ThreadState::Waiting);
|
|
||||||
|
|
||||||
// Add the thread to the queue.
|
|
||||||
wait_list.push_back(GetCurrentThread(kernel));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the thread from the wait list.
|
|
||||||
{
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
wait_list.erase(wait_list.iterator_to(GetCurrentThread(kernel)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel the task that the sleep setup.
|
|
||||||
kernel.TimeManager().UnscheduleTimeEvent(owner);
|
|
||||||
|
|
||||||
// Re-acquire the lock.
|
|
||||||
lock->Lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
KernelCore& kernel;
|
KernelCore& kernel;
|
||||||
KThread::WaiterList wait_list{};
|
KThread::WaiterList wait_list{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,44 +5,59 @@
|
||||||
#include "core/hle/kernel/k_light_lock.h"
|
#include "core/hle/kernel/k_light_lock.h"
|
||||||
#include "core/hle/kernel/k_scheduler.h"
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
#include "core/hle/kernel/k_thread_queue.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ThreadQueueImplForKLightLock final : public KThreadQueue {
|
||||||
|
public:
|
||||||
|
explicit ThreadQueueImplForKLightLock(KernelCore& kernel_) : KThreadQueue(kernel_) {}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// Remove the thread as a waiter from its owner.
|
||||||
|
if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
|
||||||
|
owner->RemoveWaiter(waiting_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void KLightLock::Lock() {
|
void KLightLock::Lock() {
|
||||||
const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
|
const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
|
||||||
const uintptr_t cur_thread_tag = (cur_thread | 1);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
uintptr_t old_tag = tag.load(std::memory_order_relaxed);
|
uintptr_t old_tag = tag.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : old_tag | 1,
|
while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : (old_tag | 1),
|
||||||
std::memory_order_acquire)) {
|
std::memory_order_acquire)) {
|
||||||
if ((old_tag | 1) == cur_thread_tag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((old_tag == 0) || ((old_tag | 1) == cur_thread_tag)) {
|
if (old_tag == 0 || this->LockSlowPath(old_tag | 1, cur_thread)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
LockSlowPath(old_tag | 1, cur_thread);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KLightLock::Unlock() {
|
void KLightLock::Unlock() {
|
||||||
const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
|
const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
|
||||||
|
|
||||||
uintptr_t expected = cur_thread;
|
uintptr_t expected = cur_thread;
|
||||||
do {
|
if (!tag.compare_exchange_strong(expected, 0, std::memory_order_release)) {
|
||||||
if (expected != cur_thread) {
|
this->UnlockSlowPath(cur_thread);
|
||||||
return UnlockSlowPath(cur_thread);
|
|
||||||
}
|
}
|
||||||
} while (!tag.compare_exchange_weak(expected, 0, std::memory_order_release));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||||
KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread);
|
KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread);
|
||||||
|
ThreadQueueImplForKLightLock wait_queue(kernel);
|
||||||
|
|
||||||
// Pend the current thread waiting on the owner thread.
|
// Pend the current thread waiting on the owner thread.
|
||||||
{
|
{
|
||||||
|
@ -50,7 +65,7 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||||
|
|
||||||
// Ensure we actually have locking to do.
|
// Ensure we actually have locking to do.
|
||||||
if (tag.load(std::memory_order_relaxed) != _owner) {
|
if (tag.load(std::memory_order_relaxed) != _owner) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the current thread as a waiter on the owner.
|
// Add the current thread as a waiter on the owner.
|
||||||
|
@ -58,22 +73,15 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||||
cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
|
cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
|
||||||
owner_thread->AddWaiter(cur_thread);
|
owner_thread->AddWaiter(cur_thread);
|
||||||
|
|
||||||
// Set thread states.
|
// Begin waiting to hold the lock.
|
||||||
cur_thread->SetState(ThreadState::Waiting);
|
cur_thread->BeginWait(std::addressof(wait_queue));
|
||||||
|
|
||||||
if (owner_thread->IsSuspended()) {
|
if (owner_thread->IsSuspended()) {
|
||||||
owner_thread->ContinueIfHasKernelWaiters();
|
owner_thread->ContinueIfHasKernelWaiters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're no longer waiting on the lock owner.
|
return true;
|
||||||
{
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
if (KThread* owner_thread = cur_thread->GetLockOwner(); owner_thread != nullptr) {
|
|
||||||
owner_thread->RemoveWaiter(cur_thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
|
void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
|
||||||
|
@ -81,22 +89,20 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
|
||||||
|
|
||||||
// Unlock.
|
// Unlock.
|
||||||
{
|
{
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
// Get the next owner.
|
// Get the next owner.
|
||||||
s32 num_waiters = 0;
|
s32 num_waiters;
|
||||||
KThread* next_owner = owner_thread->RemoveWaiterByKey(
|
KThread* next_owner = owner_thread->RemoveWaiterByKey(
|
||||||
std::addressof(num_waiters), reinterpret_cast<uintptr_t>(std::addressof(tag)));
|
std::addressof(num_waiters), reinterpret_cast<uintptr_t>(std::addressof(tag)));
|
||||||
|
|
||||||
// Pass the lock to the next owner.
|
// Pass the lock to the next owner.
|
||||||
uintptr_t next_tag = 0;
|
uintptr_t next_tag = 0;
|
||||||
if (next_owner != nullptr) {
|
if (next_owner != nullptr) {
|
||||||
next_tag = reinterpret_cast<uintptr_t>(next_owner);
|
next_tag =
|
||||||
if (num_waiters > 1) {
|
reinterpret_cast<uintptr_t>(next_owner) | static_cast<uintptr_t>(num_waiters > 1);
|
||||||
next_tag |= 0x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
next_owner->SetState(ThreadState::Runnable);
|
next_owner->EndWait(ResultSuccess);
|
||||||
|
|
||||||
if (next_owner->IsSuspended()) {
|
if (next_owner->IsSuspended()) {
|
||||||
next_owner->ContinueIfHasKernelWaiters();
|
next_owner->ContinueIfHasKernelWaiters();
|
||||||
|
@ -110,7 +116,7 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the new tag value.
|
// Write the new tag value.
|
||||||
tag.store(next_tag);
|
tag.store(next_tag, std::memory_order_release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ public:
|
||||||
|
|
||||||
void Unlock();
|
void Unlock();
|
||||||
|
|
||||||
void LockSlowPath(uintptr_t owner, uintptr_t cur_thread);
|
bool LockSlowPath(uintptr_t owner, uintptr_t cur_thread);
|
||||||
|
|
||||||
void UnlockSlowPath(uintptr_t cur_thread);
|
void UnlockSlowPath(uintptr_t cur_thread);
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,26 @@ enum class KMemoryPermission : u8 {
|
||||||
|
|
||||||
UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
|
UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
|
||||||
Svc::MemoryPermission::Execute),
|
Svc::MemoryPermission::Execute),
|
||||||
|
|
||||||
|
KernelShift = 3,
|
||||||
|
|
||||||
|
KernelRead = Read << KernelShift,
|
||||||
|
KernelWrite = Write << KernelShift,
|
||||||
|
KernelExecute = Execute << KernelShift,
|
||||||
|
|
||||||
|
NotMapped = (1 << (2 * KernelShift)),
|
||||||
|
|
||||||
|
KernelReadWrite = KernelRead | KernelWrite,
|
||||||
|
KernelReadExecute = KernelRead | KernelExecute,
|
||||||
|
|
||||||
|
UserRead = Read | KernelRead,
|
||||||
|
UserWrite = Write | KernelWrite,
|
||||||
|
UserExecute = Execute,
|
||||||
|
|
||||||
|
UserReadWrite = UserRead | UserWrite,
|
||||||
|
UserReadExecute = UserRead | UserExecute,
|
||||||
|
|
||||||
|
IpcLockChangeMask = NotMapped | UserReadWrite
|
||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission);
|
DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission);
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,10 @@ public:
|
||||||
return num_pages;
|
return num_pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t GetSize() const {
|
||||||
|
return GetNumPages() * PageSize;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
u64 addr{};
|
u64 addr{};
|
||||||
std::size_t num_pages{};
|
std::size_t num_pages{};
|
||||||
|
|
|
@ -368,6 +368,33 @@ ResultCode KPageTable::UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, st
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size,
|
||||||
|
KPageTable& src_page_table, VAddr src_addr) {
|
||||||
|
std::lock_guard lock{page_table_lock};
|
||||||
|
|
||||||
|
const std::size_t num_pages{size / PageSize};
|
||||||
|
|
||||||
|
// Check that the memory is mapped in the destination process.
|
||||||
|
size_t num_allocator_blocks;
|
||||||
|
R_TRY(CheckMemoryState(&num_allocator_blocks, dst_addr, size, KMemoryState::All,
|
||||||
|
KMemoryState::SharedCode, KMemoryPermission::UserReadWrite,
|
||||||
|
KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
|
||||||
|
KMemoryAttribute::None));
|
||||||
|
|
||||||
|
// Check that the memory is mapped in the source process.
|
||||||
|
R_TRY(src_page_table.CheckMemoryState(src_addr, size, KMemoryState::FlagCanMapProcess,
|
||||||
|
KMemoryState::FlagCanMapProcess, KMemoryPermission::None,
|
||||||
|
KMemoryPermission::None, KMemoryAttribute::All,
|
||||||
|
KMemoryAttribute::None));
|
||||||
|
|
||||||
|
CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
|
||||||
|
|
||||||
|
// Apply the memory block update.
|
||||||
|
block_manager->Update(dst_addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
|
||||||
|
KMemoryAttribute::None);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
void KPageTable::MapPhysicalMemory(KPageLinkedList& page_linked_list, VAddr start, VAddr end) {
|
void KPageTable::MapPhysicalMemory(KPageLinkedList& page_linked_list, VAddr start, VAddr end) {
|
||||||
auto node{page_linked_list.Nodes().begin()};
|
auto node{page_linked_list.Nodes().begin()};
|
||||||
PAddr map_addr{node->GetAddress()};
|
PAddr map_addr{node->GetAddress()};
|
||||||
|
@ -685,7 +712,7 @@ ResultCode KPageTable::UnmapPages(VAddr addr, KPageLinkedList& page_linked_list,
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KPageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size,
|
ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
|
||||||
KMemoryPermission perm) {
|
KMemoryPermission perm) {
|
||||||
|
|
||||||
std::lock_guard lock{page_table_lock};
|
std::lock_guard lock{page_table_lock};
|
||||||
|
@ -942,6 +969,60 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) {
|
||||||
|
std::lock_guard lock{page_table_lock};
|
||||||
|
|
||||||
|
KMemoryPermission new_perm = KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite;
|
||||||
|
|
||||||
|
KMemoryPermission old_perm{};
|
||||||
|
|
||||||
|
if (const ResultCode result{CheckMemoryState(
|
||||||
|
nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
|
||||||
|
KMemoryState::FlagCanCodeMemory, KMemoryPermission::Mask,
|
||||||
|
KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)};
|
||||||
|
result.IsError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
|
||||||
|
|
||||||
|
block_manager->UpdateLock(
|
||||||
|
addr, size / PageSize,
|
||||||
|
[](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
|
||||||
|
block->ShareToDevice(permission);
|
||||||
|
},
|
||||||
|
new_perm);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size) {
|
||||||
|
std::lock_guard lock{page_table_lock};
|
||||||
|
|
||||||
|
KMemoryPermission new_perm = KMemoryPermission::UserReadWrite;
|
||||||
|
|
||||||
|
KMemoryPermission old_perm{};
|
||||||
|
|
||||||
|
if (const ResultCode result{CheckMemoryState(
|
||||||
|
nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
|
||||||
|
KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None,
|
||||||
|
KMemoryAttribute::All, KMemoryAttribute::Locked)};
|
||||||
|
result.IsError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
|
||||||
|
|
||||||
|
block_manager->UpdateLock(
|
||||||
|
addr, size / PageSize,
|
||||||
|
[](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
|
||||||
|
block->UnshareToDevice(permission);
|
||||||
|
},
|
||||||
|
new_perm);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
|
ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
|
||||||
block_manager = std::make_unique<KMemoryBlockManager>(start, end);
|
block_manager = std::make_unique<KMemoryBlockManager>(start, end);
|
||||||
|
|
||||||
|
@ -1231,4 +1312,42 @@ ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermissi
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultCode KPageTable::CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size,
|
||||||
|
KMemoryState state_mask, KMemoryState state,
|
||||||
|
KMemoryPermission perm_mask, KMemoryPermission perm,
|
||||||
|
KMemoryAttribute attr_mask, KMemoryAttribute attr) const {
|
||||||
|
// Get information about the first block.
|
||||||
|
const VAddr last_addr = addr + size - 1;
|
||||||
|
KMemoryBlockManager::const_iterator it{block_manager->FindIterator(addr)};
|
||||||
|
KMemoryInfo info = it->GetMemoryInfo();
|
||||||
|
|
||||||
|
// If the start address isn't aligned, we need a block.
|
||||||
|
const size_t blocks_for_start_align =
|
||||||
|
(Common::AlignDown(addr, PageSize) != info.GetAddress()) ? 1 : 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// Validate against the provided masks.
|
||||||
|
R_TRY(CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
|
||||||
|
|
||||||
|
// Break once we're done.
|
||||||
|
if (last_addr <= info.GetLastAddress()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance our iterator.
|
||||||
|
it++;
|
||||||
|
info = it->GetMemoryInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the end address isn't aligned, we need a block.
|
||||||
|
const size_t blocks_for_end_align =
|
||||||
|
(Common::AlignUp(addr + size, PageSize) != info.GetEndAddress()) ? 1 : 0;
|
||||||
|
|
||||||
|
if (out_blocks_needed != nullptr) {
|
||||||
|
*out_blocks_needed = blocks_for_start_align + blocks_for_end_align;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -33,6 +33,8 @@ public:
|
||||||
KMemoryPermission perm);
|
KMemoryPermission perm);
|
||||||
ResultCode MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
|
ResultCode MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
|
||||||
ResultCode UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
|
ResultCode UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
|
||||||
|
ResultCode UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
|
||||||
|
VAddr src_addr);
|
||||||
ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
|
ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
|
||||||
ResultCode UnmapPhysicalMemory(VAddr addr, std::size_t size);
|
ResultCode UnmapPhysicalMemory(VAddr addr, std::size_t size);
|
||||||
ResultCode UnmapMemory(VAddr addr, std::size_t size);
|
ResultCode UnmapMemory(VAddr addr, std::size_t size);
|
||||||
|
@ -41,7 +43,7 @@ public:
|
||||||
ResultCode MapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state,
|
ResultCode MapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state,
|
||||||
KMemoryPermission perm);
|
KMemoryPermission perm);
|
||||||
ResultCode UnmapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state);
|
ResultCode UnmapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state);
|
||||||
ResultCode SetCodeMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
|
ResultCode SetProcessMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
|
||||||
KMemoryInfo QueryInfo(VAddr addr);
|
KMemoryInfo QueryInfo(VAddr addr);
|
||||||
ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
|
ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
|
||||||
ResultCode ResetTransferMemory(VAddr addr, std::size_t size);
|
ResultCode ResetTransferMemory(VAddr addr, std::size_t size);
|
||||||
|
@ -55,6 +57,8 @@ public:
|
||||||
KMemoryPermission perm, PAddr map_addr = 0);
|
KMemoryPermission perm, PAddr map_addr = 0);
|
||||||
ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size);
|
ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size);
|
||||||
ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
|
ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
|
||||||
|
ResultCode LockForCodeMemory(VAddr addr, std::size_t size);
|
||||||
|
ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size);
|
||||||
|
|
||||||
Common::PageTable& PageTableImpl() {
|
Common::PageTable& PageTableImpl() {
|
||||||
return page_table_impl;
|
return page_table_impl;
|
||||||
|
@ -115,6 +119,10 @@ private:
|
||||||
return CheckMemoryState(nullptr, nullptr, nullptr, addr, size, state_mask, state, perm_mask,
|
return CheckMemoryState(nullptr, nullptr, nullptr, addr, size, state_mask, state, perm_mask,
|
||||||
perm, attr_mask, attr, ignore_attr);
|
perm, attr_mask, attr, ignore_attr);
|
||||||
}
|
}
|
||||||
|
ResultCode CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size,
|
||||||
|
KMemoryState state_mask, KMemoryState state,
|
||||||
|
KMemoryPermission perm_mask, KMemoryPermission perm,
|
||||||
|
KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
|
||||||
|
|
||||||
std::recursive_mutex page_table_lock;
|
std::recursive_mutex page_table_lock;
|
||||||
std::unique_ptr<KMemoryBlockManager> block_manager;
|
std::unique_ptr<KMemoryBlockManager> block_manager;
|
||||||
|
|
|
@ -60,6 +60,7 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority
|
||||||
thread->GetContext64().cpu_registers[0] = 0;
|
thread->GetContext64().cpu_registers[0] = 0;
|
||||||
thread->GetContext32().cpu_registers[1] = thread_handle;
|
thread->GetContext32().cpu_registers[1] = thread_handle;
|
||||||
thread->GetContext64().cpu_registers[1] = thread_handle;
|
thread->GetContext64().cpu_registers[1] = thread_handle;
|
||||||
|
thread->DisableDispatch();
|
||||||
|
|
||||||
auto& kernel = system.Kernel();
|
auto& kernel = system.Kernel();
|
||||||
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
|
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
|
||||||
|
@ -227,12 +228,15 @@ void KProcess::PinCurrentThread() {
|
||||||
const s32 core_id = GetCurrentCoreId(kernel);
|
const s32 core_id = GetCurrentCoreId(kernel);
|
||||||
KThread* cur_thread = GetCurrentThreadPointer(kernel);
|
KThread* cur_thread = GetCurrentThreadPointer(kernel);
|
||||||
|
|
||||||
|
// If the thread isn't terminated, pin it.
|
||||||
|
if (!cur_thread->IsTerminationRequested()) {
|
||||||
// Pin it.
|
// Pin it.
|
||||||
PinThread(core_id, cur_thread);
|
PinThread(core_id, cur_thread);
|
||||||
cur_thread->Pin();
|
cur_thread->Pin();
|
||||||
|
|
||||||
// An update is needed.
|
// An update is needed.
|
||||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::UnpinCurrentThread() {
|
void KProcess::UnpinCurrentThread() {
|
||||||
|
@ -250,6 +254,20 @@ void KProcess::UnpinCurrentThread() {
|
||||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KProcess::UnpinThread(KThread* thread) {
|
||||||
|
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||||
|
|
||||||
|
// Get the thread's core id.
|
||||||
|
const auto core_id = thread->GetActiveCore();
|
||||||
|
|
||||||
|
// Unpin it.
|
||||||
|
UnpinThread(core_id, thread);
|
||||||
|
thread->Unpin();
|
||||||
|
|
||||||
|
// An update is needed.
|
||||||
|
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||||
|
}
|
||||||
|
|
||||||
ResultCode KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr address,
|
ResultCode KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr address,
|
||||||
[[maybe_unused]] size_t size) {
|
[[maybe_unused]] size_t size) {
|
||||||
// Lock ourselves, to prevent concurrent access.
|
// Lock ourselves, to prevent concurrent access.
|
||||||
|
@ -528,7 +546,7 @@ void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
|
||||||
std::lock_guard lock{HLE::g_hle_lock};
|
std::lock_guard lock{HLE::g_hle_lock};
|
||||||
const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
|
const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
|
||||||
KMemoryPermission permission) {
|
KMemoryPermission permission) {
|
||||||
page_table->SetCodeMemoryPermission(segment.addr + base_addr, segment.size, permission);
|
page_table->SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
|
||||||
};
|
};
|
||||||
|
|
||||||
kernel.System().Memory().WriteBlock(*this, base_addr, code_set.memory.data(),
|
kernel.System().Memory().WriteBlock(*this, base_addr, code_set.memory.data(),
|
||||||
|
|
|
@ -347,6 +347,7 @@ public:
|
||||||
|
|
||||||
void PinCurrentThread();
|
void PinCurrentThread();
|
||||||
void UnpinCurrentThread();
|
void UnpinCurrentThread();
|
||||||
|
void UnpinThread(KThread* thread);
|
||||||
|
|
||||||
KLightLock& GetStateLock() {
|
KLightLock& GetStateLock() {
|
||||||
return state_lock;
|
return state_lock;
|
||||||
|
|
|
@ -240,8 +240,8 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3
|
||||||
|
|
||||||
// If the thread is runnable, we want to change its priority in the queue.
|
// If the thread is runnable, we want to change its priority in the queue.
|
||||||
if (thread->GetRawState() == ThreadState::Runnable) {
|
if (thread->GetRawState() == ThreadState::Runnable) {
|
||||||
GetPriorityQueue(kernel).ChangePriority(
|
GetPriorityQueue(kernel).ChangePriority(old_priority,
|
||||||
old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread);
|
thread == kernel.GetCurrentEmuThread(), thread);
|
||||||
IncrementScheduledCount(thread);
|
IncrementScheduledCount(thread);
|
||||||
SetSchedulerUpdateNeeded(kernel);
|
SetSchedulerUpdateNeeded(kernel);
|
||||||
}
|
}
|
||||||
|
@ -360,7 +360,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KScheduler::CanSchedule(KernelCore& kernel) {
|
bool KScheduler::CanSchedule(KernelCore& kernel) {
|
||||||
return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1;
|
return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
|
bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
|
||||||
|
@ -376,20 +376,30 @@ void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void KScheduler::DisableScheduling(KernelCore& kernel) {
|
void KScheduler::DisableScheduling(KernelCore& kernel) {
|
||||||
if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0);
|
if (kernel.IsShuttingDown()) {
|
||||||
scheduler->GetCurrentThread()->DisableDispatch();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0);
|
||||||
|
GetCurrentThreadPointer(kernel)->DisableDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
|
void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
|
||||||
if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1);
|
if (kernel.IsShuttingDown()) {
|
||||||
if (scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1) {
|
return;
|
||||||
scheduler->GetCurrentThread()->EnableDispatch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto* current_thread = GetCurrentThreadPointer(kernel);
|
||||||
|
|
||||||
|
ASSERT(current_thread->GetDisableDispatchCount() >= 1);
|
||||||
|
|
||||||
|
if (current_thread->GetDisableDispatchCount() > 1) {
|
||||||
|
current_thread->EnableDispatch();
|
||||||
|
} else {
|
||||||
RescheduleCores(kernel, cores_needing_scheduling);
|
RescheduleCores(kernel, cores_needing_scheduling);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
||||||
|
@ -617,13 +627,17 @@ KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, c
|
||||||
state.highest_priority_thread = nullptr;
|
state.highest_priority_thread = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
KScheduler::~KScheduler() {
|
void KScheduler::Finalize() {
|
||||||
if (idle_thread) {
|
if (idle_thread) {
|
||||||
idle_thread->Close();
|
idle_thread->Close();
|
||||||
idle_thread = nullptr;
|
idle_thread = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KScheduler::~KScheduler() {
|
||||||
|
ASSERT(!idle_thread);
|
||||||
|
}
|
||||||
|
|
||||||
KThread* KScheduler::GetCurrentThread() const {
|
KThread* KScheduler::GetCurrentThread() const {
|
||||||
if (auto result = current_thread.load(); result) {
|
if (auto result = current_thread.load(); result) {
|
||||||
return result;
|
return result;
|
||||||
|
@ -642,10 +656,12 @@ void KScheduler::RescheduleCurrentCore() {
|
||||||
if (phys_core.IsInterrupted()) {
|
if (phys_core.IsInterrupted()) {
|
||||||
phys_core.ClearInterrupt();
|
phys_core.ClearInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
guard.Lock();
|
guard.Lock();
|
||||||
if (state.needs_scheduling.load()) {
|
if (state.needs_scheduling.load()) {
|
||||||
Schedule();
|
Schedule();
|
||||||
} else {
|
} else {
|
||||||
|
GetCurrentThread()->EnableDispatch();
|
||||||
guard.Unlock();
|
guard.Unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -655,26 +671,33 @@ void KScheduler::OnThreadStart() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void KScheduler::Unload(KThread* thread) {
|
void KScheduler::Unload(KThread* thread) {
|
||||||
|
ASSERT(thread);
|
||||||
|
|
||||||
LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
|
LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
|
||||||
|
|
||||||
if (thread) {
|
|
||||||
if (thread->IsCallingSvc()) {
|
if (thread->IsCallingSvc()) {
|
||||||
thread->ClearIsCallingSvc();
|
thread->ClearIsCallingSvc();
|
||||||
}
|
}
|
||||||
if (!thread->IsTerminationRequested()) {
|
|
||||||
prev_thread = thread;
|
|
||||||
|
|
||||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
auto& physical_core = system.Kernel().PhysicalCore(core_id);
|
||||||
|
if (!physical_core.IsInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::ARM_Interface& cpu_core = physical_core.ArmInterface();
|
||||||
cpu_core.SaveContext(thread->GetContext32());
|
cpu_core.SaveContext(thread->GetContext32());
|
||||||
cpu_core.SaveContext(thread->GetContext64());
|
cpu_core.SaveContext(thread->GetContext64());
|
||||||
// Save the TPIDR_EL0 system register in case it was modified.
|
// Save the TPIDR_EL0 system register in case it was modified.
|
||||||
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||||
cpu_core.ClearExclusiveState();
|
cpu_core.ClearExclusiveState();
|
||||||
|
|
||||||
|
if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) {
|
||||||
|
prev_thread = thread;
|
||||||
} else {
|
} else {
|
||||||
prev_thread = nullptr;
|
prev_thread = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread->context_guard.Unlock();
|
thread->context_guard.Unlock();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KScheduler::Reload(KThread* thread) {
|
void KScheduler::Reload(KThread* thread) {
|
||||||
|
@ -683,11 +706,6 @@ void KScheduler::Reload(KThread* thread) {
|
||||||
if (thread) {
|
if (thread) {
|
||||||
ASSERT_MSG(thread->GetState() == ThreadState::Runnable, "Thread must be runnable.");
|
ASSERT_MSG(thread->GetState() == ThreadState::Runnable, "Thread must be runnable.");
|
||||||
|
|
||||||
auto* const thread_owner_process = thread->GetOwnerProcess();
|
|
||||||
if (thread_owner_process != nullptr) {
|
|
||||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||||
cpu_core.LoadContext(thread->GetContext32());
|
cpu_core.LoadContext(thread->GetContext32());
|
||||||
cpu_core.LoadContext(thread->GetContext64());
|
cpu_core.LoadContext(thread->GetContext64());
|
||||||
|
@ -705,7 +723,7 @@ void KScheduler::SwitchContextStep2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void KScheduler::ScheduleImpl() {
|
void KScheduler::ScheduleImpl() {
|
||||||
KThread* previous_thread = current_thread.load();
|
KThread* previous_thread = GetCurrentThread();
|
||||||
KThread* next_thread = state.highest_priority_thread;
|
KThread* next_thread = state.highest_priority_thread;
|
||||||
|
|
||||||
state.needs_scheduling = false;
|
state.needs_scheduling = false;
|
||||||
|
@ -717,10 +735,15 @@ void KScheduler::ScheduleImpl() {
|
||||||
|
|
||||||
// If we're not actually switching thread, there's nothing to do.
|
// If we're not actually switching thread, there's nothing to do.
|
||||||
if (next_thread == current_thread.load()) {
|
if (next_thread == current_thread.load()) {
|
||||||
|
previous_thread->EnableDispatch();
|
||||||
guard.Unlock();
|
guard.Unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (next_thread->GetCurrentCore() != core_id) {
|
||||||
|
next_thread->SetCurrentCore(core_id);
|
||||||
|
}
|
||||||
|
|
||||||
current_thread.store(next_thread);
|
current_thread.store(next_thread);
|
||||||
|
|
||||||
KProcess* const previous_process = system.Kernel().CurrentProcess();
|
KProcess* const previous_process = system.Kernel().CurrentProcess();
|
||||||
|
@ -731,11 +754,7 @@ void KScheduler::ScheduleImpl() {
|
||||||
Unload(previous_thread);
|
Unload(previous_thread);
|
||||||
|
|
||||||
std::shared_ptr<Common::Fiber>* old_context;
|
std::shared_ptr<Common::Fiber>* old_context;
|
||||||
if (previous_thread != nullptr) {
|
|
||||||
old_context = &previous_thread->GetHostContext();
|
old_context = &previous_thread->GetHostContext();
|
||||||
} else {
|
|
||||||
old_context = &idle_thread->GetHostContext();
|
|
||||||
}
|
|
||||||
guard.Unlock();
|
guard.Unlock();
|
||||||
|
|
||||||
Common::Fiber::YieldTo(*old_context, *switch_fiber);
|
Common::Fiber::YieldTo(*old_context, *switch_fiber);
|
||||||
|
|
|
@ -33,6 +33,8 @@ public:
|
||||||
explicit KScheduler(Core::System& system_, s32 core_id_);
|
explicit KScheduler(Core::System& system_, s32 core_id_);
|
||||||
~KScheduler();
|
~KScheduler();
|
||||||
|
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||||
void RescheduleCurrentCore();
|
void RescheduleCurrentCore();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lock() {
|
void Lock() {
|
||||||
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
|
if (kernel.IsShuttingDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsLockedByCurrentThread()) {
|
if (IsLockedByCurrentThread()) {
|
||||||
// If we already own the lock, we can just increment the count.
|
// If we already own the lock, we can just increment the count.
|
||||||
ASSERT(lock_count > 0);
|
ASSERT(lock_count > 0);
|
||||||
|
@ -43,6 +48,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unlock() {
|
void Unlock() {
|
||||||
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
|
if (kernel.IsShuttingDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(IsLockedByCurrentThread());
|
ASSERT(IsLockedByCurrentThread());
|
||||||
ASSERT(lock_count > 0);
|
ASSERT(lock_count > 0);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/kernel/global_scheduler_context.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/time_manager.h"
|
#include "core/hle/kernel/time_manager.h"
|
||||||
|
|
|
@ -175,8 +175,7 @@ ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
|
||||||
{
|
{
|
||||||
KScopedSchedulerLock lock(kernel);
|
KScopedSchedulerLock lock(kernel);
|
||||||
if (!context.IsThreadWaiting()) {
|
if (!context.IsThreadWaiting()) {
|
||||||
context.GetThread().Wakeup();
|
context.GetThread().EndWait(result);
|
||||||
context.GetThread().SetSyncedObject(nullptr, result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,66 @@
|
||||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||||
#include "core/hle/kernel/k_synchronization_object.h"
|
#include "core/hle/kernel/k_synchronization_object.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
#include "core/hle/kernel/k_thread_queue.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/svc_results.h"
|
#include "core/hle/kernel/svc_results.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ThreadQueueImplForKSynchronizationObjectWait final : public KThreadQueueWithoutEndWait {
|
||||||
|
public:
|
||||||
|
ThreadQueueImplForKSynchronizationObjectWait(KernelCore& kernel_, KSynchronizationObject** o,
|
||||||
|
KSynchronizationObject::ThreadListNode* n, s32 c)
|
||||||
|
: KThreadQueueWithoutEndWait(kernel_), m_objects(o), m_nodes(n), m_count(c) {}
|
||||||
|
|
||||||
|
void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object,
|
||||||
|
ResultCode wait_result) override {
|
||||||
|
// Determine the sync index, and unlink all nodes.
|
||||||
|
s32 sync_index = -1;
|
||||||
|
for (auto i = 0; i < m_count; ++i) {
|
||||||
|
// Check if this is the signaled object.
|
||||||
|
if (m_objects[i] == signaled_object && sync_index == -1) {
|
||||||
|
sync_index = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlink the current node from the current object.
|
||||||
|
m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the waiting thread's sync index.
|
||||||
|
waiting_thread->SetSyncedIndex(sync_index);
|
||||||
|
|
||||||
|
// Set the waiting thread as not cancellable.
|
||||||
|
waiting_thread->ClearCancellable();
|
||||||
|
|
||||||
|
// Invoke the base end wait handler.
|
||||||
|
KThreadQueue::EndWait(waiting_thread, wait_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// Remove all nodes from our list.
|
||||||
|
for (auto i = 0; i < m_count; ++i) {
|
||||||
|
m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the waiting thread as not cancellable.
|
||||||
|
waiting_thread->ClearCancellable();
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KSynchronizationObject** m_objects;
|
||||||
|
KSynchronizationObject::ThreadListNode* m_nodes;
|
||||||
|
s32 m_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void KSynchronizationObject::Finalize() {
|
void KSynchronizationObject::Finalize() {
|
||||||
this->OnFinalizeSynchronizationObject();
|
this->OnFinalizeSynchronizationObject();
|
||||||
KAutoObject::Finalize();
|
KAutoObject::Finalize();
|
||||||
|
@ -25,11 +80,19 @@ ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
|
||||||
std::vector<ThreadListNode> thread_nodes(num_objects);
|
std::vector<ThreadListNode> thread_nodes(num_objects);
|
||||||
|
|
||||||
// Prepare for wait.
|
// Prepare for wait.
|
||||||
KThread* thread = kernel_ctx.CurrentScheduler()->GetCurrentThread();
|
KThread* thread = GetCurrentThreadPointer(kernel_ctx);
|
||||||
|
ThreadQueueImplForKSynchronizationObjectWait wait_queue(kernel_ctx, objects,
|
||||||
|
thread_nodes.data(), num_objects);
|
||||||
|
|
||||||
{
|
{
|
||||||
// Setup the scheduling lock and sleep.
|
// Setup the scheduling lock and sleep.
|
||||||
KScopedSchedulerLockAndSleep slp{kernel_ctx, thread, timeout};
|
KScopedSchedulerLockAndSleep slp(kernel_ctx, thread, timeout);
|
||||||
|
|
||||||
|
// Check if the thread should terminate.
|
||||||
|
if (thread->IsTerminationRequested()) {
|
||||||
|
slp.CancelSleep();
|
||||||
|
return ResultTerminationRequested;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any of the objects are already signaled.
|
// Check if any of the objects are already signaled.
|
||||||
for (auto i = 0; i < num_objects; ++i) {
|
for (auto i = 0; i < num_objects; ++i) {
|
||||||
|
@ -48,12 +111,6 @@ ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
|
||||||
return ResultTimedOut;
|
return ResultTimedOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the thread should terminate.
|
|
||||||
if (thread->IsTerminationRequested()) {
|
|
||||||
slp.CancelSleep();
|
|
||||||
return ResultTerminationRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if waiting was canceled.
|
// Check if waiting was canceled.
|
||||||
if (thread->IsWaitCancelled()) {
|
if (thread->IsWaitCancelled()) {
|
||||||
slp.CancelSleep();
|
slp.CancelSleep();
|
||||||
|
@ -66,73 +123,25 @@ ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
|
||||||
thread_nodes[i].thread = thread;
|
thread_nodes[i].thread = thread;
|
||||||
thread_nodes[i].next = nullptr;
|
thread_nodes[i].next = nullptr;
|
||||||
|
|
||||||
if (objects[i]->thread_list_tail == nullptr) {
|
objects[i]->LinkNode(std::addressof(thread_nodes[i]));
|
||||||
objects[i]->thread_list_head = std::addressof(thread_nodes[i]);
|
|
||||||
} else {
|
|
||||||
objects[i]->thread_list_tail->next = std::addressof(thread_nodes[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
objects[i]->thread_list_tail = std::addressof(thread_nodes[i]);
|
// Mark the thread as cancellable.
|
||||||
}
|
|
||||||
|
|
||||||
// For debugging only
|
|
||||||
thread->SetWaitObjectsForDebugging({objects, static_cast<std::size_t>(num_objects)});
|
|
||||||
|
|
||||||
// Mark the thread as waiting.
|
|
||||||
thread->SetCancellable();
|
thread->SetCancellable();
|
||||||
thread->SetSyncedObject(nullptr, ResultTimedOut);
|
|
||||||
thread->SetState(ThreadState::Waiting);
|
// Clear the thread's synced index.
|
||||||
|
thread->SetSyncedIndex(-1);
|
||||||
|
|
||||||
|
// Wait for an object to be signaled.
|
||||||
|
thread->BeginWait(std::addressof(wait_queue));
|
||||||
thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization);
|
thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The lock/sleep is done, so we should be able to get our result.
|
// Set the output index.
|
||||||
|
*out_index = thread->GetSyncedIndex();
|
||||||
// Thread is no longer cancellable.
|
|
||||||
thread->ClearCancellable();
|
|
||||||
|
|
||||||
// For debugging only
|
|
||||||
thread->SetWaitObjectsForDebugging({});
|
|
||||||
|
|
||||||
// Cancel the timer as needed.
|
|
||||||
kernel_ctx.TimeManager().UnscheduleTimeEvent(thread);
|
|
||||||
|
|
||||||
// Get the wait result.
|
// Get the wait result.
|
||||||
ResultCode wait_result{ResultSuccess};
|
return thread->GetWaitResult();
|
||||||
s32 sync_index = -1;
|
|
||||||
{
|
|
||||||
KScopedSchedulerLock lock(kernel_ctx);
|
|
||||||
KSynchronizationObject* synced_obj;
|
|
||||||
wait_result = thread->GetWaitResult(std::addressof(synced_obj));
|
|
||||||
|
|
||||||
for (auto i = 0; i < num_objects; ++i) {
|
|
||||||
// Unlink the object from the list.
|
|
||||||
ThreadListNode* prev_ptr =
|
|
||||||
reinterpret_cast<ThreadListNode*>(std::addressof(objects[i]->thread_list_head));
|
|
||||||
ThreadListNode* prev_val = nullptr;
|
|
||||||
ThreadListNode *prev, *tail_prev;
|
|
||||||
|
|
||||||
do {
|
|
||||||
prev = prev_ptr;
|
|
||||||
prev_ptr = prev_ptr->next;
|
|
||||||
tail_prev = prev_val;
|
|
||||||
prev_val = prev_ptr;
|
|
||||||
} while (prev_ptr != std::addressof(thread_nodes[i]));
|
|
||||||
|
|
||||||
if (objects[i]->thread_list_tail == std::addressof(thread_nodes[i])) {
|
|
||||||
objects[i]->thread_list_tail = tail_prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev->next = thread_nodes[i].next;
|
|
||||||
|
|
||||||
if (objects[i] == synced_obj) {
|
|
||||||
sync_index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set output.
|
|
||||||
*out_index = sync_index;
|
|
||||||
return wait_result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KSynchronizationObject::KSynchronizationObject(KernelCore& kernel_)
|
KSynchronizationObject::KSynchronizationObject(KernelCore& kernel_)
|
||||||
|
@ -141,7 +150,7 @@ KSynchronizationObject::KSynchronizationObject(KernelCore& kernel_)
|
||||||
KSynchronizationObject::~KSynchronizationObject() = default;
|
KSynchronizationObject::~KSynchronizationObject() = default;
|
||||||
|
|
||||||
void KSynchronizationObject::NotifyAvailable(ResultCode result) {
|
void KSynchronizationObject::NotifyAvailable(ResultCode result) {
|
||||||
KScopedSchedulerLock lock(kernel);
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
// If we're not signaled, we've nothing to notify.
|
// If we're not signaled, we've nothing to notify.
|
||||||
if (!this->IsSignaled()) {
|
if (!this->IsSignaled()) {
|
||||||
|
@ -150,11 +159,7 @@ void KSynchronizationObject::NotifyAvailable(ResultCode result) {
|
||||||
|
|
||||||
// Iterate over each thread.
|
// Iterate over each thread.
|
||||||
for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
|
for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
|
||||||
KThread* thread = cur_node->thread;
|
cur_node->thread->NotifyAvailable(this, result);
|
||||||
if (thread->GetState() == ThreadState::Waiting) {
|
|
||||||
thread->SetSyncedObject(this, result);
|
|
||||||
thread->SetState(ThreadState::Runnable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,38 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] std::vector<KThread*> GetWaitingThreadsForDebugging() const;
|
[[nodiscard]] std::vector<KThread*> GetWaitingThreadsForDebugging() const;
|
||||||
|
|
||||||
|
void LinkNode(ThreadListNode* node_) {
|
||||||
|
// Link the node to the list.
|
||||||
|
if (thread_list_tail == nullptr) {
|
||||||
|
thread_list_head = node_;
|
||||||
|
} else {
|
||||||
|
thread_list_tail->next = node_;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_list_tail = node_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnlinkNode(ThreadListNode* node_) {
|
||||||
|
// Unlink the node from the list.
|
||||||
|
ThreadListNode* prev_ptr =
|
||||||
|
reinterpret_cast<ThreadListNode*>(std::addressof(thread_list_head));
|
||||||
|
ThreadListNode* prev_val = nullptr;
|
||||||
|
ThreadListNode *prev, *tail_prev;
|
||||||
|
|
||||||
|
do {
|
||||||
|
prev = prev_ptr;
|
||||||
|
prev_ptr = prev_ptr->next;
|
||||||
|
tail_prev = prev_val;
|
||||||
|
prev_val = prev_ptr;
|
||||||
|
} while (prev_ptr != node_);
|
||||||
|
|
||||||
|
if (thread_list_tail == node_) {
|
||||||
|
thread_list_tail = tail_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev->next = node_->next;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit KSynchronizationObject(KernelCore& kernel);
|
explicit KSynchronizationObject(KernelCore& kernel);
|
||||||
~KSynchronizationObject() override;
|
~KSynchronizationObject() override;
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/fiber.h"
|
#include "common/fiber.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/thread_queue_list.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/cpu_manager.h"
|
#include "core/cpu_manager.h"
|
||||||
#include "core/hardware_properties.h"
|
#include "core/hardware_properties.h"
|
||||||
|
@ -56,6 +59,34 @@ static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context,
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait {
|
||||||
|
public:
|
||||||
|
explicit ThreadQueueImplForKThreadSleep(KernelCore& kernel_)
|
||||||
|
: KThreadQueueWithoutEndWait(kernel_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ThreadQueueImplForKThreadSetProperty final : public KThreadQueue {
|
||||||
|
public:
|
||||||
|
explicit ThreadQueueImplForKThreadSetProperty(KernelCore& kernel_, KThread::WaiterList* wl)
|
||||||
|
: KThreadQueue(kernel_), m_wait_list(wl) {}
|
||||||
|
|
||||||
|
void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) override {
|
||||||
|
// Remove the thread from the wait list.
|
||||||
|
m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
|
||||||
|
|
||||||
|
// Invoke the base cancel wait handler.
|
||||||
|
KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KThread::WaiterList* m_wait_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
KThread::KThread(KernelCore& kernel_)
|
KThread::KThread(KernelCore& kernel_)
|
||||||
: KAutoObjectWithSlabHeapAndContainer{kernel_}, activity_pause_lock{kernel_} {}
|
: KAutoObjectWithSlabHeapAndContainer{kernel_}, activity_pause_lock{kernel_} {}
|
||||||
KThread::~KThread() = default;
|
KThread::~KThread() = default;
|
||||||
|
@ -82,6 +113,8 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case ThreadType::HighPriority:
|
case ThreadType::HighPriority:
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
case ThreadType::Dummy:
|
||||||
|
[[fallthrough]];
|
||||||
case ThreadType::User:
|
case ThreadType::User:
|
||||||
ASSERT(((owner == nullptr) ||
|
ASSERT(((owner == nullptr) ||
|
||||||
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
|
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
|
||||||
|
@ -127,11 +160,8 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
|
||||||
priority = prio;
|
priority = prio;
|
||||||
base_priority = prio;
|
base_priority = prio;
|
||||||
|
|
||||||
// Set sync object and waiting lock to null.
|
|
||||||
synced_object = nullptr;
|
|
||||||
|
|
||||||
// Initialize sleeping queue.
|
// Initialize sleeping queue.
|
||||||
sleeping_queue = nullptr;
|
wait_queue = nullptr;
|
||||||
|
|
||||||
// Set suspend flags.
|
// Set suspend flags.
|
||||||
suspend_request_flags = 0;
|
suspend_request_flags = 0;
|
||||||
|
@ -184,7 +214,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
|
||||||
// Setup the stack parameters.
|
// Setup the stack parameters.
|
||||||
StackParameters& sp = GetStackParameters();
|
StackParameters& sp = GetStackParameters();
|
||||||
sp.cur_thread = this;
|
sp.cur_thread = this;
|
||||||
sp.disable_count = 1;
|
sp.disable_count = 0;
|
||||||
SetInExceptionHandler();
|
SetInExceptionHandler();
|
||||||
|
|
||||||
// Set thread ID.
|
// Set thread ID.
|
||||||
|
@ -211,15 +241,16 @@ ResultCode KThread::InitializeThread(KThread* thread, KThreadFunction func, uint
|
||||||
// Initialize the thread.
|
// Initialize the thread.
|
||||||
R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type));
|
R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type));
|
||||||
|
|
||||||
// Initialize host context.
|
// Initialize emulation parameters.
|
||||||
thread->host_context =
|
thread->host_context =
|
||||||
std::make_shared<Common::Fiber>(std::move(init_func), init_func_parameter);
|
std::make_shared<Common::Fiber>(std::move(init_func), init_func_parameter);
|
||||||
|
thread->is_single_core = !Settings::values.use_multi_core.GetValue();
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KThread::InitializeDummyThread(KThread* thread) {
|
ResultCode KThread::InitializeDummyThread(KThread* thread) {
|
||||||
return thread->Initialize({}, {}, {}, DefaultThreadPriority, 3, {}, ThreadType::Main);
|
return thread->Initialize({}, {}, {}, DefaultThreadPriority, 3, {}, ThreadType::Dummy);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
|
ResultCode KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
|
||||||
|
@ -273,11 +304,14 @@ void KThread::Finalize() {
|
||||||
|
|
||||||
auto it = waiter_list.begin();
|
auto it = waiter_list.begin();
|
||||||
while (it != waiter_list.end()) {
|
while (it != waiter_list.end()) {
|
||||||
// The thread shouldn't be a kernel waiter.
|
// Clear the lock owner
|
||||||
it->SetLockOwner(nullptr);
|
it->SetLockOwner(nullptr);
|
||||||
it->SetSyncedObject(nullptr, ResultInvalidState);
|
|
||||||
it->Wakeup();
|
// Erase the waiter from our list.
|
||||||
it = waiter_list.erase(it);
|
it = waiter_list.erase(it);
|
||||||
|
|
||||||
|
// Cancel the thread's wait.
|
||||||
|
it->CancelWait(ResultInvalidState, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,15 +328,12 @@ bool KThread::IsSignaled() const {
|
||||||
return signaled;
|
return signaled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KThread::Wakeup() {
|
void KThread::OnTimer() {
|
||||||
KScopedSchedulerLock sl{kernel};
|
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||||
|
|
||||||
|
// If we're waiting, cancel the wait.
|
||||||
if (GetState() == ThreadState::Waiting) {
|
if (GetState() == ThreadState::Waiting) {
|
||||||
if (sleeping_queue != nullptr) {
|
wait_queue->CancelWait(this, ResultTimedOut, false);
|
||||||
sleeping_queue->WakeupThread(this);
|
|
||||||
} else {
|
|
||||||
SetState(ThreadState::Runnable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +358,7 @@ void KThread::StartTermination() {
|
||||||
|
|
||||||
// Signal.
|
// Signal.
|
||||||
signaled = true;
|
signaled = true;
|
||||||
NotifyAvailable();
|
KSynchronizationObject::NotifyAvailable();
|
||||||
|
|
||||||
// Clear previous thread in KScheduler.
|
// Clear previous thread in KScheduler.
|
||||||
KScheduler::ClearPreviousThread(kernel, this);
|
KScheduler::ClearPreviousThread(kernel, this);
|
||||||
|
@ -475,30 +506,32 @@ ResultCode KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_m
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
|
ResultCode KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) {
|
||||||
ASSERT(parent != nullptr);
|
ASSERT(parent != nullptr);
|
||||||
ASSERT(v_affinity_mask != 0);
|
ASSERT(v_affinity_mask != 0);
|
||||||
KScopedLightLock lk{activity_pause_lock};
|
KScopedLightLock lk(activity_pause_lock);
|
||||||
|
|
||||||
// Set the core mask.
|
// Set the core mask.
|
||||||
u64 p_affinity_mask = 0;
|
u64 p_affinity_mask = 0;
|
||||||
{
|
{
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl(kernel);
|
||||||
ASSERT(num_core_migration_disables >= 0);
|
ASSERT(num_core_migration_disables >= 0);
|
||||||
|
|
||||||
// If the core id is no-update magic, preserve the ideal core id.
|
// If we're updating, set our ideal virtual core.
|
||||||
if (cpu_core_id == Svc::IdealCoreNoUpdate) {
|
if (core_id_ != Svc::IdealCoreNoUpdate) {
|
||||||
cpu_core_id = virtual_ideal_core_id;
|
virtual_ideal_core_id = core_id_;
|
||||||
R_UNLESS(((1ULL << cpu_core_id) & v_affinity_mask) != 0, ResultInvalidCombination);
|
} else {
|
||||||
|
// Preserve our ideal core id.
|
||||||
|
core_id_ = virtual_ideal_core_id;
|
||||||
|
R_UNLESS(((1ULL << core_id_) & v_affinity_mask) != 0, ResultInvalidCombination);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the virtual core/affinity mask.
|
// Set our affinity mask.
|
||||||
virtual_ideal_core_id = cpu_core_id;
|
|
||||||
virtual_affinity_mask = v_affinity_mask;
|
virtual_affinity_mask = v_affinity_mask;
|
||||||
|
|
||||||
// Translate the virtual core to a physical core.
|
// Translate the virtual core to a physical core.
|
||||||
if (cpu_core_id >= 0) {
|
if (core_id_ >= 0) {
|
||||||
cpu_core_id = Core::Hardware::VirtualToPhysicalCoreMap[cpu_core_id];
|
core_id_ = Core::Hardware::VirtualToPhysicalCoreMap[core_id_];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the virtual affinity mask to a physical one.
|
// Translate the virtual affinity mask to a physical one.
|
||||||
|
@ -513,7 +546,7 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
|
||||||
const KAffinityMask old_mask = physical_affinity_mask;
|
const KAffinityMask old_mask = physical_affinity_mask;
|
||||||
|
|
||||||
// Set our new ideals.
|
// Set our new ideals.
|
||||||
physical_ideal_core_id = cpu_core_id;
|
physical_ideal_core_id = core_id_;
|
||||||
physical_affinity_mask.SetAffinityMask(p_affinity_mask);
|
physical_affinity_mask.SetAffinityMask(p_affinity_mask);
|
||||||
|
|
||||||
if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) {
|
if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) {
|
||||||
|
@ -531,18 +564,18 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we edit the original affinity for restoration later.
|
// Otherwise, we edit the original affinity for restoration later.
|
||||||
original_physical_ideal_core_id = cpu_core_id;
|
original_physical_ideal_core_id = core_id_;
|
||||||
original_physical_affinity_mask.SetAffinityMask(p_affinity_mask);
|
original_physical_affinity_mask.SetAffinityMask(p_affinity_mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the pinned waiter list.
|
// Update the pinned waiter list.
|
||||||
|
ThreadQueueImplForKThreadSetProperty wait_queue_(kernel, std::addressof(pinned_waiter_list));
|
||||||
{
|
{
|
||||||
bool retry_update{};
|
bool retry_update{};
|
||||||
bool thread_is_pinned{};
|
|
||||||
do {
|
do {
|
||||||
// Lock the scheduler.
|
// Lock the scheduler.
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
// Don't do any further management if our termination has been requested.
|
// Don't do any further management if our termination has been requested.
|
||||||
R_SUCCEED_IF(IsTerminationRequested());
|
R_SUCCEED_IF(IsTerminationRequested());
|
||||||
|
@ -570,12 +603,9 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
|
||||||
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
|
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
|
||||||
ResultTerminationRequested);
|
ResultTerminationRequested);
|
||||||
|
|
||||||
// Note that the thread was pinned.
|
|
||||||
thread_is_pinned = true;
|
|
||||||
|
|
||||||
// Wait until the thread isn't pinned any more.
|
// Wait until the thread isn't pinned any more.
|
||||||
pinned_waiter_list.push_back(GetCurrentThread(kernel));
|
pinned_waiter_list.push_back(GetCurrentThread(kernel));
|
||||||
GetCurrentThread(kernel).SetState(ThreadState::Waiting);
|
GetCurrentThread(kernel).BeginWait(std::addressof(wait_queue_));
|
||||||
} else {
|
} else {
|
||||||
// If the thread isn't pinned, release the scheduler lock and retry until it's
|
// If the thread isn't pinned, release the scheduler lock and retry until it's
|
||||||
// not current.
|
// not current.
|
||||||
|
@ -583,16 +613,6 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (retry_update);
|
} while (retry_update);
|
||||||
|
|
||||||
// If the thread was pinned, it no longer is, and we should remove the current thread from
|
|
||||||
// our waiter list.
|
|
||||||
if (thread_is_pinned) {
|
|
||||||
// Lock the scheduler.
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
// Remove from the list.
|
|
||||||
pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
|
@ -641,15 +661,9 @@ void KThread::WaitCancel() {
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl{kernel};
|
||||||
|
|
||||||
// Check if we're waiting and cancellable.
|
// Check if we're waiting and cancellable.
|
||||||
if (GetState() == ThreadState::Waiting && cancellable) {
|
if (this->GetState() == ThreadState::Waiting && cancellable) {
|
||||||
if (sleeping_queue != nullptr) {
|
|
||||||
sleeping_queue->WakeupThread(this);
|
|
||||||
wait_cancelled = true;
|
|
||||||
} else {
|
|
||||||
SetSyncedObject(nullptr, ResultCancelled);
|
|
||||||
SetState(ThreadState::Runnable);
|
|
||||||
wait_cancelled = false;
|
wait_cancelled = false;
|
||||||
}
|
wait_queue->CancelWait(this, ResultCancelled, true);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, note that we cancelled a wait.
|
// Otherwise, note that we cancelled a wait.
|
||||||
wait_cancelled = true;
|
wait_cancelled = true;
|
||||||
|
@ -700,60 +714,59 @@ ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
|
||||||
// Set the activity.
|
// Set the activity.
|
||||||
{
|
{
|
||||||
// Lock the scheduler.
|
// Lock the scheduler.
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
// Verify our state.
|
// Verify our state.
|
||||||
const auto cur_state = GetState();
|
const auto cur_state = this->GetState();
|
||||||
R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable),
|
R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable),
|
||||||
ResultInvalidState);
|
ResultInvalidState);
|
||||||
|
|
||||||
// Either pause or resume.
|
// Either pause or resume.
|
||||||
if (activity == Svc::ThreadActivity::Paused) {
|
if (activity == Svc::ThreadActivity::Paused) {
|
||||||
// Verify that we're not suspended.
|
// Verify that we're not suspended.
|
||||||
R_UNLESS(!IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
|
R_UNLESS(!this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
|
||||||
|
|
||||||
// Suspend.
|
// Suspend.
|
||||||
RequestSuspend(SuspendType::Thread);
|
this->RequestSuspend(SuspendType::Thread);
|
||||||
} else {
|
} else {
|
||||||
ASSERT(activity == Svc::ThreadActivity::Runnable);
|
ASSERT(activity == Svc::ThreadActivity::Runnable);
|
||||||
|
|
||||||
// Verify that we're suspended.
|
// Verify that we're suspended.
|
||||||
R_UNLESS(IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
|
R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
|
||||||
|
|
||||||
// Resume.
|
// Resume.
|
||||||
Resume(SuspendType::Thread);
|
this->Resume(SuspendType::Thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the thread is now paused, update the pinned waiter list.
|
// If the thread is now paused, update the pinned waiter list.
|
||||||
if (activity == Svc::ThreadActivity::Paused) {
|
if (activity == Svc::ThreadActivity::Paused) {
|
||||||
bool thread_is_pinned{};
|
ThreadQueueImplForKThreadSetProperty wait_queue_(kernel,
|
||||||
bool thread_is_current{};
|
std::addressof(pinned_waiter_list));
|
||||||
|
|
||||||
|
bool thread_is_current;
|
||||||
do {
|
do {
|
||||||
// Lock the scheduler.
|
// Lock the scheduler.
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
// Don't do any further management if our termination has been requested.
|
// Don't do any further management if our termination has been requested.
|
||||||
R_SUCCEED_IF(IsTerminationRequested());
|
R_SUCCEED_IF(this->IsTerminationRequested());
|
||||||
|
|
||||||
|
// By default, treat the thread as not current.
|
||||||
|
thread_is_current = false;
|
||||||
|
|
||||||
// Check whether the thread is pinned.
|
// Check whether the thread is pinned.
|
||||||
if (GetStackParameters().is_pinned) {
|
if (this->GetStackParameters().is_pinned) {
|
||||||
// Verify that the current thread isn't terminating.
|
// Verify that the current thread isn't terminating.
|
||||||
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
|
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
|
||||||
ResultTerminationRequested);
|
ResultTerminationRequested);
|
||||||
|
|
||||||
// Note that the thread was pinned and not current.
|
|
||||||
thread_is_pinned = true;
|
|
||||||
thread_is_current = false;
|
|
||||||
|
|
||||||
// Wait until the thread isn't pinned any more.
|
// Wait until the thread isn't pinned any more.
|
||||||
pinned_waiter_list.push_back(GetCurrentThread(kernel));
|
pinned_waiter_list.push_back(GetCurrentThread(kernel));
|
||||||
GetCurrentThread(kernel).SetState(ThreadState::Waiting);
|
GetCurrentThread(kernel).BeginWait(std::addressof(wait_queue_));
|
||||||
} else {
|
} else {
|
||||||
// Check if the thread is currently running.
|
// Check if the thread is currently running.
|
||||||
// If it is, we'll need to retry.
|
// If it is, we'll need to retry.
|
||||||
thread_is_current = false;
|
|
||||||
|
|
||||||
for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
|
for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
|
||||||
if (kernel.Scheduler(i).GetCurrentThread() == this) {
|
if (kernel.Scheduler(i).GetCurrentThread() == this) {
|
||||||
thread_is_current = true;
|
thread_is_current = true;
|
||||||
|
@ -762,16 +775,6 @@ ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (thread_is_current);
|
} while (thread_is_current);
|
||||||
|
|
||||||
// If the thread was pinned, it no longer is, and we should remove the current thread from
|
|
||||||
// our waiter list.
|
|
||||||
if (thread_is_pinned) {
|
|
||||||
// Lock the scheduler.
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
// Remove from the list.
|
|
||||||
pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
|
@ -966,6 +969,9 @@ ResultCode KThread::Run() {
|
||||||
|
|
||||||
// Set our state and finish.
|
// Set our state and finish.
|
||||||
SetState(ThreadState::Runnable);
|
SetState(ThreadState::Runnable);
|
||||||
|
|
||||||
|
DisableDispatch();
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -996,29 +1002,63 @@ ResultCode KThread::Sleep(s64 timeout) {
|
||||||
ASSERT(this == GetCurrentThreadPointer(kernel));
|
ASSERT(this == GetCurrentThreadPointer(kernel));
|
||||||
ASSERT(timeout > 0);
|
ASSERT(timeout > 0);
|
||||||
|
|
||||||
|
ThreadQueueImplForKThreadSleep wait_queue_(kernel);
|
||||||
{
|
{
|
||||||
// Setup the scheduling lock and sleep.
|
// Setup the scheduling lock and sleep.
|
||||||
KScopedSchedulerLockAndSleep slp{kernel, this, timeout};
|
KScopedSchedulerLockAndSleep slp(kernel, this, timeout);
|
||||||
|
|
||||||
// Check if the thread should terminate.
|
// Check if the thread should terminate.
|
||||||
if (IsTerminationRequested()) {
|
if (this->IsTerminationRequested()) {
|
||||||
slp.CancelSleep();
|
slp.CancelSleep();
|
||||||
return ResultTerminationRequested;
|
return ResultTerminationRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the thread as waiting.
|
// Wait for the sleep to end.
|
||||||
SetState(ThreadState::Waiting);
|
this->BeginWait(std::addressof(wait_queue_));
|
||||||
SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep);
|
SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The lock/sleep is done.
|
|
||||||
|
|
||||||
// Cancel the timer.
|
|
||||||
kernel.TimeManager().UnscheduleTimeEvent(this);
|
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KThread::BeginWait(KThreadQueue* queue) {
|
||||||
|
// Set our state as waiting.
|
||||||
|
SetState(ThreadState::Waiting);
|
||||||
|
|
||||||
|
// Set our wait queue.
|
||||||
|
wait_queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
|
||||||
|
// Lock the scheduler.
|
||||||
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
|
// If we're waiting, notify our queue that we're available.
|
||||||
|
if (GetState() == ThreadState::Waiting) {
|
||||||
|
wait_queue->NotifyAvailable(this, signaled_object, wait_result_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThread::EndWait(ResultCode wait_result_) {
|
||||||
|
// Lock the scheduler.
|
||||||
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
|
// If we're waiting, notify our queue that we're available.
|
||||||
|
if (GetState() == ThreadState::Waiting) {
|
||||||
|
wait_queue->EndWait(this, wait_result_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThread::CancelWait(ResultCode wait_result_, bool cancel_timer_task) {
|
||||||
|
// Lock the scheduler.
|
||||||
|
KScopedSchedulerLock sl(kernel);
|
||||||
|
|
||||||
|
// If we're waiting, notify our queue that we're available.
|
||||||
|
if (GetState() == ThreadState::Waiting) {
|
||||||
|
wait_queue->CancelWait(this, wait_result_, cancel_timer_task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void KThread::SetState(ThreadState state) {
|
void KThread::SetState(ThreadState state) {
|
||||||
KScopedSchedulerLock sl{kernel};
|
KScopedSchedulerLock sl{kernel};
|
||||||
|
|
||||||
|
@ -1050,4 +1090,26 @@ s32 GetCurrentCoreId(KernelCore& kernel) {
|
||||||
return GetCurrentThread(kernel).GetCurrentCore();
|
return GetCurrentThread(kernel).GetCurrentCore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KScopedDisableDispatch::~KScopedDisableDispatch() {
|
||||||
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
|
if (kernel.IsShuttingDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the reschedule if single-core, as dispatch tracking is disabled here.
|
||||||
|
if (!Settings::values.use_multi_core.GetValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) {
|
||||||
|
auto scheduler = kernel.CurrentScheduler();
|
||||||
|
|
||||||
|
if (scheduler) {
|
||||||
|
scheduler->RescheduleCurrentCore();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GetCurrentThread(kernel).EnableDispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -48,6 +48,7 @@ enum class ThreadType : u32 {
|
||||||
Kernel = 1,
|
Kernel = 1,
|
||||||
HighPriority = 2,
|
HighPriority = 2,
|
||||||
User = 3,
|
User = 3,
|
||||||
|
Dummy = 100, // Special thread type for emulation purposes only
|
||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(ThreadType);
|
DECLARE_ENUM_FLAG_OPERATORS(ThreadType);
|
||||||
|
|
||||||
|
@ -161,8 +162,6 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wakeup();
|
|
||||||
|
|
||||||
void SetBasePriority(s32 value);
|
void SetBasePriority(s32 value);
|
||||||
|
|
||||||
[[nodiscard]] ResultCode Run();
|
[[nodiscard]] ResultCode Run();
|
||||||
|
@ -197,13 +196,19 @@ public:
|
||||||
|
|
||||||
void Suspend();
|
void Suspend();
|
||||||
|
|
||||||
void SetSyncedObject(KSynchronizationObject* obj, ResultCode wait_res) {
|
constexpr void SetSyncedIndex(s32 index) {
|
||||||
synced_object = obj;
|
synced_index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr s32 GetSyncedIndex() const {
|
||||||
|
return synced_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void SetWaitResult(ResultCode wait_res) {
|
||||||
wait_result = wait_res;
|
wait_result = wait_res;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] ResultCode GetWaitResult(KSynchronizationObject** out) const {
|
[[nodiscard]] constexpr ResultCode GetWaitResult() const {
|
||||||
*out = synced_object;
|
|
||||||
return wait_result;
|
return wait_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,6 +379,8 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool IsSignaled() const override;
|
[[nodiscard]] bool IsSignaled() const override;
|
||||||
|
|
||||||
|
void OnTimer();
|
||||||
|
|
||||||
static void PostDestroy(uintptr_t arg);
|
static void PostDestroy(uintptr_t arg);
|
||||||
|
|
||||||
[[nodiscard]] static ResultCode InitializeDummyThread(KThread* thread);
|
[[nodiscard]] static ResultCode InitializeDummyThread(KThread* thread);
|
||||||
|
@ -446,20 +453,39 @@ public:
|
||||||
return per_core_priority_queue_entry[core];
|
return per_core_priority_queue_entry[core];
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetSleepingQueue(KThreadQueue* q) {
|
[[nodiscard]] bool IsKernelThread() const {
|
||||||
sleeping_queue = q;
|
return GetActiveCore() == 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsDispatchTrackingDisabled() const {
|
||||||
|
return is_single_core || IsKernelThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] s32 GetDisableDispatchCount() const {
|
[[nodiscard]] s32 GetDisableDispatchCount() const {
|
||||||
|
if (IsDispatchTrackingDisabled()) {
|
||||||
|
// TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return this->GetStackParameters().disable_count;
|
return this->GetStackParameters().disable_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisableDispatch() {
|
void DisableDispatch() {
|
||||||
|
if (IsDispatchTrackingDisabled()) {
|
||||||
|
// TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
|
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
|
||||||
this->GetStackParameters().disable_count++;
|
this->GetStackParameters().disable_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableDispatch() {
|
void EnableDispatch() {
|
||||||
|
if (IsDispatchTrackingDisabled()) {
|
||||||
|
// TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
|
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
|
||||||
this->GetStackParameters().disable_count--;
|
this->GetStackParameters().disable_count--;
|
||||||
}
|
}
|
||||||
|
@ -573,6 +599,15 @@ public:
|
||||||
address_key_value = val;
|
address_key_value = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClearWaitQueue() {
|
||||||
|
wait_queue = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginWait(KThreadQueue* queue);
|
||||||
|
void NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_);
|
||||||
|
void EndWait(ResultCode wait_result_);
|
||||||
|
void CancelWait(ResultCode wait_result_, bool cancel_timer_task);
|
||||||
|
|
||||||
[[nodiscard]] bool HasWaiters() const {
|
[[nodiscard]] bool HasWaiters() const {
|
||||||
return !waiter_list.empty();
|
return !waiter_list.empty();
|
||||||
}
|
}
|
||||||
|
@ -667,7 +702,6 @@ private:
|
||||||
KAffinityMask physical_affinity_mask{};
|
KAffinityMask physical_affinity_mask{};
|
||||||
u64 thread_id{};
|
u64 thread_id{};
|
||||||
std::atomic<s64> cpu_time{};
|
std::atomic<s64> cpu_time{};
|
||||||
KSynchronizationObject* synced_object{};
|
|
||||||
VAddr address_key{};
|
VAddr address_key{};
|
||||||
KProcess* parent{};
|
KProcess* parent{};
|
||||||
VAddr kernel_stack_top{};
|
VAddr kernel_stack_top{};
|
||||||
|
@ -677,13 +711,14 @@ private:
|
||||||
s64 schedule_count{};
|
s64 schedule_count{};
|
||||||
s64 last_scheduled_tick{};
|
s64 last_scheduled_tick{};
|
||||||
std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{};
|
std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{};
|
||||||
KThreadQueue* sleeping_queue{};
|
KThreadQueue* wait_queue{};
|
||||||
WaiterList waiter_list{};
|
WaiterList waiter_list{};
|
||||||
WaiterList pinned_waiter_list{};
|
WaiterList pinned_waiter_list{};
|
||||||
KThread* lock_owner{};
|
KThread* lock_owner{};
|
||||||
u32 address_key_value{};
|
u32 address_key_value{};
|
||||||
u32 suspend_request_flags{};
|
u32 suspend_request_flags{};
|
||||||
u32 suspend_allowed_flags{};
|
u32 suspend_allowed_flags{};
|
||||||
|
s32 synced_index{};
|
||||||
ResultCode wait_result{ResultSuccess};
|
ResultCode wait_result{ResultSuccess};
|
||||||
s32 base_priority{};
|
s32 base_priority{};
|
||||||
s32 physical_ideal_core_id{};
|
s32 physical_ideal_core_id{};
|
||||||
|
@ -708,6 +743,7 @@ private:
|
||||||
|
|
||||||
// For emulation
|
// For emulation
|
||||||
std::shared_ptr<Common::Fiber> host_context{};
|
std::shared_ptr<Common::Fiber> host_context{};
|
||||||
|
bool is_single_core{};
|
||||||
|
|
||||||
// For debugging
|
// For debugging
|
||||||
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
|
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
|
||||||
|
@ -752,4 +788,20 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class KScopedDisableDispatch {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] explicit KScopedDisableDispatch(KernelCore& kernel_) : kernel{kernel_} {
|
||||||
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
|
if (kernel.IsShuttingDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GetCurrentThread(kernel).DisableDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
~KScopedDisableDispatch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
KernelCore& kernel;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_thread_queue.h"
|
||||||
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
#include "core/hle/kernel/time_manager.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
void KThreadQueue::NotifyAvailable([[maybe_unused]] KThread* waiting_thread,
|
||||||
|
[[maybe_unused]] KSynchronizationObject* signaled_object,
|
||||||
|
[[maybe_unused]] ResultCode wait_result) {}
|
||||||
|
|
||||||
|
void KThreadQueue::EndWait(KThread* waiting_thread, ResultCode wait_result) {
|
||||||
|
// Set the thread's wait result.
|
||||||
|
waiting_thread->SetWaitResult(wait_result);
|
||||||
|
|
||||||
|
// Set the thread as runnable.
|
||||||
|
waiting_thread->SetState(ThreadState::Runnable);
|
||||||
|
|
||||||
|
// Clear the thread's wait queue.
|
||||||
|
waiting_thread->ClearWaitQueue();
|
||||||
|
|
||||||
|
// Cancel the thread task.
|
||||||
|
kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThreadQueue::CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
|
bool cancel_timer_task) {
|
||||||
|
// Set the thread's wait result.
|
||||||
|
waiting_thread->SetWaitResult(wait_result);
|
||||||
|
|
||||||
|
// Set the thread as runnable.
|
||||||
|
waiting_thread->SetState(ThreadState::Runnable);
|
||||||
|
|
||||||
|
// Clear the thread's wait queue.
|
||||||
|
waiting_thread->ClearWaitQueue();
|
||||||
|
|
||||||
|
// Cancel the thread task.
|
||||||
|
if (cancel_timer_task) {
|
||||||
|
kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KThreadQueueWithoutEndWait::EndWait([[maybe_unused]] KThread* waiting_thread,
|
||||||
|
[[maybe_unused]] ResultCode wait_result) {}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
@ -11,71 +12,24 @@ namespace Kernel {
|
||||||
class KThreadQueue {
|
class KThreadQueue {
|
||||||
public:
|
public:
|
||||||
explicit KThreadQueue(KernelCore& kernel_) : kernel{kernel_} {}
|
explicit KThreadQueue(KernelCore& kernel_) : kernel{kernel_} {}
|
||||||
|
virtual ~KThreadQueue() = default;
|
||||||
|
|
||||||
bool IsEmpty() const {
|
virtual void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object,
|
||||||
return wait_list.empty();
|
ResultCode wait_result);
|
||||||
}
|
virtual void EndWait(KThread* waiting_thread, ResultCode wait_result);
|
||||||
|
virtual void CancelWait(KThread* waiting_thread, ResultCode wait_result,
|
||||||
KThread::WaiterList::iterator begin() {
|
bool cancel_timer_task);
|
||||||
return wait_list.begin();
|
|
||||||
}
|
|
||||||
KThread::WaiterList::iterator end() {
|
|
||||||
return wait_list.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SleepThread(KThread* t) {
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
// If the thread needs terminating, don't enqueue it.
|
|
||||||
if (t->IsTerminationRequested()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the thread's queue and mark it as waiting.
|
|
||||||
t->SetSleepingQueue(this);
|
|
||||||
t->SetState(ThreadState::Waiting);
|
|
||||||
|
|
||||||
// Add the thread to the queue.
|
|
||||||
wait_list.push_back(*t);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WakeupThread(KThread* t) {
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
// Remove the thread from the queue.
|
|
||||||
wait_list.erase(wait_list.iterator_to(*t));
|
|
||||||
|
|
||||||
// Mark the thread as no longer sleeping.
|
|
||||||
t->SetState(ThreadState::Runnable);
|
|
||||||
t->SetSleepingQueue(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
KThread* WakeupFrontThread() {
|
|
||||||
KScopedSchedulerLock sl{kernel};
|
|
||||||
|
|
||||||
if (wait_list.empty()) {
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
// Remove the thread from the queue.
|
|
||||||
auto it = wait_list.begin();
|
|
||||||
KThread* thread = std::addressof(*it);
|
|
||||||
wait_list.erase(it);
|
|
||||||
|
|
||||||
ASSERT(thread->GetState() == ThreadState::Waiting);
|
|
||||||
|
|
||||||
// Mark the thread as no longer sleeping.
|
|
||||||
thread->SetState(ThreadState::Runnable);
|
|
||||||
thread->SetSleepingQueue(nullptr);
|
|
||||||
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KernelCore& kernel;
|
KernelCore& kernel;
|
||||||
KThread::WaiterList wait_list{};
|
KThread::WaiterList wait_list{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class KThreadQueueWithoutEndWait : public KThreadQueue {
|
||||||
|
public:
|
||||||
|
explicit KThreadQueueWithoutEndWait(KernelCore& kernel_) : KThreadQueue(kernel_) {}
|
||||||
|
|
||||||
|
void EndWait(KThread* waiting_thread, ResultCode wait_result) override final;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "common/thread_worker.h"
|
#include "common/thread_worker.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
|
@ -83,12 +84,16 @@ struct KernelCore::Impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeCores() {
|
void InitializeCores() {
|
||||||
for (auto& core : cores) {
|
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||||
core.Initialize(current_process->Is64BitProcess());
|
cores[core_id].Initialize(current_process->Is64BitProcess());
|
||||||
|
system.Memory().SetCurrentPageTable(*current_process, core_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
|
is_shutting_down.store(true, std::memory_order_relaxed);
|
||||||
|
SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); });
|
||||||
|
|
||||||
process_list.clear();
|
process_list.clear();
|
||||||
|
|
||||||
// Close all open server ports.
|
// Close all open server ports.
|
||||||
|
@ -123,15 +128,6 @@ struct KernelCore::Impl {
|
||||||
next_user_process_id = KProcess::ProcessIDMin;
|
next_user_process_id = KProcess::ProcessIDMin;
|
||||||
next_thread_id = 1;
|
next_thread_id = 1;
|
||||||
|
|
||||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
|
||||||
if (suspend_threads[core_id]) {
|
|
||||||
suspend_threads[core_id]->Close();
|
|
||||||
suspend_threads[core_id] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
schedulers[core_id].reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
cores.clear();
|
cores.clear();
|
||||||
|
|
||||||
global_handle_table->Finalize();
|
global_handle_table->Finalize();
|
||||||
|
@ -159,6 +155,16 @@ struct KernelCore::Impl {
|
||||||
CleanupObject(time_shared_mem);
|
CleanupObject(time_shared_mem);
|
||||||
CleanupObject(system_resource_limit);
|
CleanupObject(system_resource_limit);
|
||||||
|
|
||||||
|
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||||
|
if (suspend_threads[core_id]) {
|
||||||
|
suspend_threads[core_id]->Close();
|
||||||
|
suspend_threads[core_id] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
schedulers[core_id]->Finalize();
|
||||||
|
schedulers[core_id].reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
|
// Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
|
||||||
next_host_thread_id = Core::Hardware::NUM_CPU_CORES;
|
next_host_thread_id = Core::Hardware::NUM_CPU_CORES;
|
||||||
|
|
||||||
|
@ -245,13 +251,11 @@ struct KernelCore::Impl {
|
||||||
KScopedSchedulerLock lock(kernel);
|
KScopedSchedulerLock lock(kernel);
|
||||||
global_scheduler_context->PreemptThreads();
|
global_scheduler_context->PreemptThreads();
|
||||||
}
|
}
|
||||||
const auto time_interval = std::chrono::nanoseconds{
|
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
|
||||||
Core::Timing::msToCycles(std::chrono::milliseconds(10))};
|
|
||||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto time_interval =
|
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
|
||||||
std::chrono::nanoseconds{Core::Timing::msToCycles(std::chrono::milliseconds(10))};
|
|
||||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,14 +271,6 @@ struct KernelCore::Impl {
|
||||||
|
|
||||||
void MakeCurrentProcess(KProcess* process) {
|
void MakeCurrentProcess(KProcess* process) {
|
||||||
current_process = process;
|
current_process = process;
|
||||||
if (process == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const u32 core_id = GetCurrentHostThreadID();
|
|
||||||
if (core_id < Core::Hardware::NUM_CPU_CORES) {
|
|
||||||
system.Memory().SetCurrentPageTable(*process, core_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline thread_local u32 host_thread_id = UINT32_MAX;
|
static inline thread_local u32 host_thread_id = UINT32_MAX;
|
||||||
|
@ -300,15 +296,16 @@ struct KernelCore::Impl {
|
||||||
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
|
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
|
||||||
KThread* GetHostDummyThread() {
|
KThread* GetHostDummyThread() {
|
||||||
auto make_thread = [this]() {
|
auto make_thread = [this]() {
|
||||||
std::unique_ptr<KThread> thread = std::make_unique<KThread>(system.Kernel());
|
std::lock_guard lk(dummy_thread_lock);
|
||||||
|
auto& thread = dummy_threads.emplace_back(std::make_unique<KThread>(system.Kernel()));
|
||||||
KAutoObject::Create(thread.get());
|
KAutoObject::Create(thread.get());
|
||||||
ASSERT(KThread::InitializeDummyThread(thread.get()).IsSuccess());
|
ASSERT(KThread::InitializeDummyThread(thread.get()).IsSuccess());
|
||||||
thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
|
thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
|
||||||
return thread;
|
return thread.get();
|
||||||
};
|
};
|
||||||
|
|
||||||
thread_local auto thread = make_thread();
|
thread_local KThread* saved_thread = make_thread();
|
||||||
return thread.get();
|
return saved_thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a CPU core thread by allocating a host thread ID for it
|
/// Registers a CPU core thread by allocating a host thread ID for it
|
||||||
|
@ -343,7 +340,16 @@ struct KernelCore::Impl {
|
||||||
is_phantom_mode_for_singlecore = value;
|
is_phantom_mode_for_singlecore = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsShuttingDown() const {
|
||||||
|
return is_shutting_down.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
KThread* GetCurrentEmuThread() {
|
KThread* GetCurrentEmuThread() {
|
||||||
|
// If we are shutting down the kernel, none of this is relevant anymore.
|
||||||
|
if (IsShuttingDown()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const auto thread_id = GetCurrentHostThreadID();
|
const auto thread_id = GetCurrentHostThreadID();
|
||||||
if (thread_id >= Core::Hardware::NUM_CPU_CORES) {
|
if (thread_id >= Core::Hardware::NUM_CPU_CORES) {
|
||||||
return GetHostDummyThread();
|
return GetHostDummyThread();
|
||||||
|
@ -695,6 +701,12 @@ struct KernelCore::Impl {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::mutex server_ports_lock;
|
||||||
|
std::mutex server_sessions_lock;
|
||||||
|
std::mutex registered_objects_lock;
|
||||||
|
std::mutex registered_in_use_objects_lock;
|
||||||
|
std::mutex dummy_thread_lock;
|
||||||
|
|
||||||
std::atomic<u32> next_object_id{0};
|
std::atomic<u32> next_object_id{0};
|
||||||
std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
|
std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
|
||||||
std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
|
std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
|
||||||
|
@ -725,10 +737,6 @@ struct KernelCore::Impl {
|
||||||
std::unordered_set<KServerSession*> server_sessions;
|
std::unordered_set<KServerSession*> server_sessions;
|
||||||
std::unordered_set<KAutoObject*> registered_objects;
|
std::unordered_set<KAutoObject*> registered_objects;
|
||||||
std::unordered_set<KAutoObject*> registered_in_use_objects;
|
std::unordered_set<KAutoObject*> registered_in_use_objects;
|
||||||
std::mutex server_ports_lock;
|
|
||||||
std::mutex server_sessions_lock;
|
|
||||||
std::mutex registered_objects_lock;
|
|
||||||
std::mutex registered_in_use_objects_lock;
|
|
||||||
|
|
||||||
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
|
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
|
||||||
std::vector<Kernel::PhysicalCore> cores;
|
std::vector<Kernel::PhysicalCore> cores;
|
||||||
|
@ -753,7 +761,11 @@ struct KernelCore::Impl {
|
||||||
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
||||||
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
||||||
|
|
||||||
|
// Specifically tracked to be automatically destroyed with kernel
|
||||||
|
std::vector<std::unique_ptr<KThread>> dummy_threads;
|
||||||
|
|
||||||
bool is_multicore{};
|
bool is_multicore{};
|
||||||
|
std::atomic_bool is_shutting_down{};
|
||||||
bool is_phantom_mode_for_singlecore{};
|
bool is_phantom_mode_for_singlecore{};
|
||||||
u32 single_core_thread_id{};
|
u32 single_core_thread_id{};
|
||||||
|
|
||||||
|
@ -839,16 +851,20 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
|
||||||
return impl->cores[id];
|
return impl->cores[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t KernelCore::CurrentPhysicalCoreIndex() const {
|
||||||
|
const u32 core_id = impl->GetCurrentHostThreadID();
|
||||||
|
if (core_id >= Core::Hardware::NUM_CPU_CORES) {
|
||||||
|
return Core::Hardware::NUM_CPU_CORES - 1;
|
||||||
|
}
|
||||||
|
return core_id;
|
||||||
|
}
|
||||||
|
|
||||||
Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
|
Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
|
||||||
u32 core_id = impl->GetCurrentHostThreadID();
|
return impl->cores[CurrentPhysicalCoreIndex()];
|
||||||
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
|
|
||||||
return impl->cores[core_id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
|
const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
|
||||||
u32 core_id = impl->GetCurrentHostThreadID();
|
return impl->cores[CurrentPhysicalCoreIndex()];
|
||||||
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
|
|
||||||
return impl->cores[core_id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::KScheduler* KernelCore::CurrentScheduler() {
|
Kernel::KScheduler* KernelCore::CurrentScheduler() {
|
||||||
|
@ -1051,6 +1067,9 @@ void KernelCore::Suspend(bool in_suspention) {
|
||||||
impl->suspend_threads[core_id]->SetState(state);
|
impl->suspend_threads[core_id]->SetState(state);
|
||||||
impl->suspend_threads[core_id]->SetWaitReasonForDebugging(
|
impl->suspend_threads[core_id]->SetWaitReasonForDebugging(
|
||||||
ThreadWaitReasonForDebugging::Suspended);
|
ThreadWaitReasonForDebugging::Suspended);
|
||||||
|
if (!should_suspend) {
|
||||||
|
impl->suspend_threads[core_id]->DisableDispatch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1059,19 +1078,21 @@ bool KernelCore::IsMulticore() const {
|
||||||
return impl->is_multicore;
|
return impl->is_multicore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool KernelCore::IsShuttingDown() const {
|
||||||
|
return impl->IsShuttingDown();
|
||||||
|
}
|
||||||
|
|
||||||
void KernelCore::ExceptionalExit() {
|
void KernelCore::ExceptionalExit() {
|
||||||
exception_exited = true;
|
exception_exited = true;
|
||||||
Suspend(true);
|
Suspend(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelCore::EnterSVCProfile() {
|
void KernelCore::EnterSVCProfile() {
|
||||||
std::size_t core = impl->GetCurrentHostThreadID();
|
impl->svc_ticks[CurrentPhysicalCoreIndex()] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC));
|
||||||
impl->svc_ticks[core] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelCore::ExitSVCProfile() {
|
void KernelCore::ExitSVCProfile() {
|
||||||
std::size_t core = impl->GetCurrentHostThreadID();
|
MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[CurrentPhysicalCoreIndex()]);
|
||||||
MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
|
std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
|
||||||
|
|
|
@ -53,6 +53,7 @@ class KSharedMemoryInfo;
|
||||||
class KThread;
|
class KThread;
|
||||||
class KTransferMemory;
|
class KTransferMemory;
|
||||||
class KWritableEvent;
|
class KWritableEvent;
|
||||||
|
class KCodeMemory;
|
||||||
class PhysicalCore;
|
class PhysicalCore;
|
||||||
class ServiceThread;
|
class ServiceThread;
|
||||||
class Synchronization;
|
class Synchronization;
|
||||||
|
@ -148,6 +149,9 @@ public:
|
||||||
/// Gets the an instance of the respective physical CPU core.
|
/// Gets the an instance of the respective physical CPU core.
|
||||||
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
|
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
|
||||||
|
|
||||||
|
/// Gets the current physical core index for the running host thread.
|
||||||
|
std::size_t CurrentPhysicalCoreIndex() const;
|
||||||
|
|
||||||
/// Gets the sole instance of the Scheduler at the current running core.
|
/// Gets the sole instance of the Scheduler at the current running core.
|
||||||
Kernel::KScheduler* CurrentScheduler();
|
Kernel::KScheduler* CurrentScheduler();
|
||||||
|
|
||||||
|
@ -271,6 +275,8 @@ public:
|
||||||
|
|
||||||
bool IsMulticore() const;
|
bool IsMulticore() const;
|
||||||
|
|
||||||
|
bool IsShuttingDown() const;
|
||||||
|
|
||||||
void EnterSVCProfile();
|
void EnterSVCProfile();
|
||||||
|
|
||||||
void ExitSVCProfile();
|
void ExitSVCProfile();
|
||||||
|
@ -326,6 +332,8 @@ public:
|
||||||
return slab_heap_container->transfer_memory;
|
return slab_heap_container->transfer_memory;
|
||||||
} else if constexpr (std::is_same_v<T, KWritableEvent>) {
|
} else if constexpr (std::is_same_v<T, KWritableEvent>) {
|
||||||
return slab_heap_container->writeable_event;
|
return slab_heap_container->writeable_event;
|
||||||
|
} else if constexpr (std::is_same_v<T, KCodeMemory>) {
|
||||||
|
return slab_heap_container->code_memory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +385,7 @@ private:
|
||||||
KSlabHeap<KThread> thread;
|
KSlabHeap<KThread> thread;
|
||||||
KSlabHeap<KTransferMemory> transfer_memory;
|
KSlabHeap<KTransferMemory> transfer_memory;
|
||||||
KSlabHeap<KWritableEvent> writeable_event;
|
KSlabHeap<KWritableEvent> writeable_event;
|
||||||
|
KSlabHeap<KCodeMemory> code_memory;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<SlabHeapContainer> slab_heap_container;
|
std::unique_ptr<SlabHeapContainer> slab_heap_container;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue