Merge branch 'yuzu-emu:master' into convert_legacy
This commit is contained in:
commit
e49184e606
360 changed files with 43056 additions and 27212 deletions
|
@ -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>
|
||||||
|
|
1646
dist/languages/ca.ts
vendored
1646
dist/languages/ca.ts
vendored
File diff suppressed because it is too large
Load diff
1664
dist/languages/cs.ts
vendored
1664
dist/languages/cs.ts
vendored
File diff suppressed because it is too large
Load diff
1642
dist/languages/da.ts
vendored
1642
dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load diff
1666
dist/languages/de.ts
vendored
1666
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load diff
2623
dist/languages/es.ts
vendored
2623
dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load diff
1700
dist/languages/fr.ts
vendored
1700
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load diff
1678
dist/languages/it.ts
vendored
1678
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load diff
1676
dist/languages/ja_JP.ts
vendored
1676
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load diff
1682
dist/languages/ko_KR.ts
vendored
1682
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load diff
1634
dist/languages/nb.ts
vendored
1634
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load diff
1654
dist/languages/nl.ts
vendored
1654
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load diff
1650
dist/languages/pl.ts
vendored
1650
dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load diff
1672
dist/languages/pt_BR.ts
vendored
1672
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load diff
1648
dist/languages/pt_PT.ts
vendored
1648
dist/languages/pt_PT.ts
vendored
File diff suppressed because it is too large
Load diff
1790
dist/languages/ru_RU.ts
vendored
1790
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load diff
1646
dist/languages/sv.ts
vendored
1646
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load diff
1797
dist/languages/tr_TR.ts
vendored
1797
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load diff
6044
dist/languages/vi_VN.ts
vendored
Normal file
6044
dist/languages/vi_VN.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
1674
dist/languages/zh_CN.ts
vendored
1674
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load diff
1852
dist/languages/zh_TW.ts
vendored
1852
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load diff
4
externals/CMakeLists.txt
vendored
4
externals/CMakeLists.txt
vendored
|
@ -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)
|
||||||
|
|
2
externals/SDL
vendored
2
externals/SDL
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 25f9ed87ff6947d9576fc9d79dee0784e638ac58
|
Subproject commit 2e9821423a237a1206e3c09020778faacfe430be
|
2
externals/cubeb
vendored
2
externals/cubeb
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 1d66483ad2b93f0e00e175f9480c771af90003a7
|
Subproject commit 75d9d125ee655ef80f3bfcd97ae5a805931042b8
|
18
externals/find-modules/FindUnicorn.cmake
vendored
18
externals/find-modules/FindUnicorn.cmake
vendored
|
@ -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>
|
||||||
|
|
366
src/common/input.h
Normal file
366
src/common/input.h
Normal file
|
@ -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
|
|
232
src/core/hid/emulated_console.cpp
Normal file
232
src/core/hid/emulated_console.cpp
Normal file
|
@ -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
|
190
src/core/hid/emulated_console.h
Normal file
190
src/core/hid/emulated_console.h
Normal file
|
@ -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
|
1139
src/core/hid/emulated_controller.cpp
Normal file
1139
src/core/hid/emulated_controller.cpp
Normal file
File diff suppressed because it is too large
Load diff
411
src/core/hid/emulated_controller.h
Normal file
411
src/core/hid/emulated_controller.h
Normal file
|
@ -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
|
459
src/core/hid/emulated_devices.cpp
Normal file
459
src/core/hid/emulated_devices.cpp
Normal file
|
@ -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
|
210
src/core/hid/emulated_devices.h
Normal file
210
src/core/hid/emulated_devices.h
Normal file
|
@ -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
|
214
src/core/hid/hid_core.cpp
Normal file
214
src/core/hid/hid_core.cpp
Normal file
|
@ -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
|
82
src/core/hid/hid_core.h
Normal file
82
src/core/hid/hid_core.h
Normal file
|
@ -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
|
635
src/core/hid/hid_types.h
Normal file
635
src/core/hid/hid_types.h
Normal file
|
@ -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
|
383
src/core/hid/input_converter.cpp
Normal file
383
src/core/hid/input_converter.cpp
Normal file
|
@ -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
|
96
src/core/hid/input_converter.h
Normal file
96
src/core/hid/input_converter.h
Normal file
|
@ -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.
|
||||||
|
|
||||||
|
|
146
src/core/hle/kernel/k_code_memory.cpp
Normal file
146
src/core/hle/kernel/k_code_memory.cpp
Normal file
|
@ -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
|
66
src/core/hle/kernel/k_code_memory.h
Normal file
66
src/core/hle/kernel/k_code_memory.h
Normal file
|
@ -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.
|
||||||
|
|
80
src/core/hle/kernel/k_light_condition_variable.cpp
Normal file
80
src/core/hle/kernel/k_light_condition_variable.cpp
Normal file
|
@ -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,6 +228,8 @@ 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();
|
||||||
|
@ -234,6 +237,7 @@ void KProcess::PinCurrentThread() {
|
||||||
// An update is needed.
|
// An update is needed.
|
||||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void KProcess::UnpinCurrentThread() {
|
void KProcess::UnpinCurrentThread() {
|
||||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||||
|
@ -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,21 +376,31 @@ 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) {
|
||||||
if (IsSchedulerUpdateNeeded(kernel)) {
|
if (IsSchedulerUpdateNeeded(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,27 +671,34 @@ 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) {
|
||||||
LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread ? thread->GetName() : "nullptr");
|
LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread ? thread->GetName() : "nullptr");
|
||||||
|
@ -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
|
||||||
|
|
49
src/core/hle/kernel/k_thread_queue.cpp
Normal file
49
src/core/hle/kernel/k_thread_queue.cpp
Normal file
|
@ -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 a new issue