Fix merge conflicts?

merge-requests/60/head
Ameer 2020-07-04 00:59:40 +07:00
commit f829932ed1
237 changed files with 7841 additions and 2788 deletions

3
.gitmodules vendored

@ -37,3 +37,6 @@
[submodule "externals/libusb"] [submodule "externals/libusb"]
path = externals/libusb path = externals/libusb
url = https://github.com/ameerj/libusb url = https://github.com/ameerj/libusb
[submodule "opus"]
path = externals/opus/opus
url = https://github.com/xiph/opus.git

@ -156,8 +156,6 @@ macro(yuzu_find_packages)
#"libzip 1.5 libzip/1.5.2@bincrafters/stable" #"libzip 1.5 libzip/1.5.2@bincrafters/stable"
"lz4 1.8 lz4/1.9.2" "lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.7 nlohmann_json/3.7.3" "nlohmann_json 3.7 nlohmann_json/3.7.3"
# we need to be careful as the version check might be broken https://github.com/xiph/opus/issues/110
"opus 1.3 opus/1.3.1"
"ZLIB 1.2 zlib/1.2.11" "ZLIB 1.2 zlib/1.2.11"
"zstd 1.4 zstd/1.4.4" "zstd 1.4 zstd/1.4.4"
) )
@ -214,6 +212,9 @@ if(ENABLE_QT)
set(QT_PREFIX_HINT HINTS "${QT_PREFIX}") set(QT_PREFIX_HINT HINTS "${QT_PREFIX}")
endif() endif()
find_package(Qt5 5.9 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) find_package(Qt5 5.9 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
if (YUZU_USE_QT_WEB_ENGINE)
find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
endif()
if (NOT Qt5_FOUND) if (NOT Qt5_FOUND)
list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable") list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable")
endif() endif()

80
dist/yuzu.manifest vendored

@ -1,24 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assembly manifestVersion="1.0"
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> xmlns="urn:schemas-microsoft-com:asm.v1"
<security> xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<requestedPrivileges> <asmv3:application>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/> <asmv3:windowsSettings>
</requestedPrivileges> <!-- Windows 7/8/8.1/10 -->
</security> <dpiAware
</trustInfo> xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<application xmlns="urn:schemas-microsoft-com:asm.v3"> true/pm
<windowsSettings> </dpiAware>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> <!-- Windows 10, version 1607 or later -->
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware> <dpiAwareness
</windowsSettings> xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
</application> PerMonitorV2
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> </dpiAwareness>
<application> <!-- Windows 10, version 1703 or later -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> <gdiScaling
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> true
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> </gdiScaling>
</application> <ws2:longPathAware
</compatibility> xmlns:ws3="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
</assembly> true
</ws2:longPathAware>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility
xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<trustInfo
xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<!--
UAC settings:
- app should run at same integrity level as calling process
- app does not need to manipulate windows belonging to
higher-integrity-level processes
-->
<requestedExecutionLevel
level="asInvoker"
uiAccess="false"
/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

@ -91,3 +91,6 @@ if (ENABLE_WEB_SERVICE)
target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT) target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
endif() endif()
# Opus
add_subdirectory(opus)

@ -1 +1 @@
Subproject commit e7166e8ba74d7b9c85e87afc0aaf667e7e84cfe0 Subproject commit 4f967387c07365b7ea35d2fa3e19b7df8872a09b

@ -0,0 +1,254 @@
cmake_minimum_required(VERSION 3.8)
project(opus)
option(OPUS_STACK_PROTECTOR "Use stack protection" OFF)
option(OPUS_USE_ALLOCA "Use alloca for stack arrays (on non-C99 compilers)" OFF)
option(OPUS_CUSTOM_MODES "Enable non-Opus modes, e.g. 44.1 kHz & 2^n frames" OFF)
option(OPUS_FIXED_POINT "Compile as fixed-point (for machines without a fast enough FPU)" OFF)
option(OPUS_ENABLE_FLOAT_API "Compile with the floating point API (for machines with float library" ON)
include(opus/opus_functions.cmake)
if(OPUS_STACK_PROTECTOR)
if(NOT MSVC) # GC on by default on MSVC
check_and_set_flag(STACK_PROTECTION_STRONG -fstack-protector-strong)
endif()
else()
if(MSVC)
check_and_set_flag(BUFFER_SECURITY_CHECK /GS-)
endif()
endif()
add_library(opus STATIC
# CELT sources
opus/celt/bands.c
opus/celt/celt.c
opus/celt/celt_decoder.c
opus/celt/celt_encoder.c
opus/celt/celt_lpc.c
opus/celt/cwrs.c
opus/celt/entcode.c
opus/celt/entdec.c
opus/celt/entenc.c
opus/celt/kiss_fft.c
opus/celt/laplace.c
opus/celt/mathops.c
opus/celt/mdct.c
opus/celt/modes.c
opus/celt/pitch.c
opus/celt/quant_bands.c
opus/celt/rate.c
opus/celt/vq.c
# SILK sources
opus/silk/A2NLSF.c
opus/silk/CNG.c
opus/silk/HP_variable_cutoff.c
opus/silk/LPC_analysis_filter.c
opus/silk/LPC_fit.c
opus/silk/LPC_inv_pred_gain.c
opus/silk/LP_variable_cutoff.c
opus/silk/NLSF2A.c
opus/silk/NLSF_VQ.c
opus/silk/NLSF_VQ_weights_laroia.c
opus/silk/NLSF_decode.c
opus/silk/NLSF_del_dec_quant.c
opus/silk/NLSF_encode.c
opus/silk/NLSF_stabilize.c
opus/silk/NLSF_unpack.c
opus/silk/NSQ.c
opus/silk/NSQ_del_dec.c
opus/silk/PLC.c
opus/silk/VAD.c
opus/silk/VQ_WMat_EC.c
opus/silk/ana_filt_bank_1.c
opus/silk/biquad_alt.c
opus/silk/bwexpander.c
opus/silk/bwexpander_32.c
opus/silk/check_control_input.c
opus/silk/code_signs.c
opus/silk/control_SNR.c
opus/silk/control_audio_bandwidth.c
opus/silk/control_codec.c
opus/silk/dec_API.c
opus/silk/decode_core.c
opus/silk/decode_frame.c
opus/silk/decode_indices.c
opus/silk/decode_parameters.c
opus/silk/decode_pitch.c
opus/silk/decode_pulses.c
opus/silk/decoder_set_fs.c
opus/silk/enc_API.c
opus/silk/encode_indices.c
opus/silk/encode_pulses.c
opus/silk/gain_quant.c
opus/silk/init_decoder.c
opus/silk/init_encoder.c
opus/silk/inner_prod_aligned.c
opus/silk/interpolate.c
opus/silk/lin2log.c
opus/silk/log2lin.c
opus/silk/pitch_est_tables.c
opus/silk/process_NLSFs.c
opus/silk/quant_LTP_gains.c
opus/silk/resampler.c
opus/silk/resampler_down2.c
opus/silk/resampler_down2_3.c
opus/silk/resampler_private_AR2.c
opus/silk/resampler_private_IIR_FIR.c
opus/silk/resampler_private_down_FIR.c
opus/silk/resampler_private_up2_HQ.c
opus/silk/resampler_rom.c
opus/silk/shell_coder.c
opus/silk/sigm_Q15.c
opus/silk/sort.c
opus/silk/stereo_LR_to_MS.c
opus/silk/stereo_MS_to_LR.c
opus/silk/stereo_decode_pred.c
opus/silk/stereo_encode_pred.c
opus/silk/stereo_find_predictor.c
opus/silk/stereo_quant_pred.c
opus/silk/sum_sqr_shift.c
opus/silk/table_LSF_cos.c
opus/silk/tables_LTP.c
opus/silk/tables_NLSF_CB_NB_MB.c
opus/silk/tables_NLSF_CB_WB.c
opus/silk/tables_gain.c
opus/silk/tables_other.c
opus/silk/tables_pitch_lag.c
opus/silk/tables_pulses_per_block.c
# Opus sources
opus/src/analysis.c
opus/src/mapping_matrix.c
opus/src/mlp.c
opus/src/mlp_data.c
opus/src/opus.c
opus/src/opus_decoder.c
opus/src/opus_encoder.c
opus/src/opus_multistream.c
opus/src/opus_multistream_decoder.c
opus/src/opus_multistream_encoder.c
opus/src/opus_projection_decoder.c
opus/src/opus_projection_encoder.c
opus/src/repacketizer.c
)
if (DEBUG)
target_sources(opus PRIVATE opus/silk/debug.c)
endif()
if (OPUS_FIXED_POINT)
target_sources(opus PRIVATE
opus/silk/fixed/LTP_analysis_filter_FIX.c
opus/silk/fixed/LTP_scale_ctrl_FIX.c
opus/silk/fixed/apply_sine_window_FIX.c
opus/silk/fixed/autocorr_FIX.c
opus/silk/fixed/burg_modified_FIX.c
opus/silk/fixed/corrMatrix_FIX.c
opus/silk/fixed/encode_frame_FIX.c
opus/silk/fixed/find_LPC_FIX.c
opus/silk/fixed/find_LTP_FIX.c
opus/silk/fixed/find_pitch_lags_FIX.c
opus/silk/fixed/find_pred_coefs_FIX.c
opus/silk/fixed/k2a_FIX.c
opus/silk/fixed/k2a_Q16_FIX.c
opus/silk/fixed/noise_shape_analysis_FIX.c
opus/silk/fixed/pitch_analysis_core_FIX.c
opus/silk/fixed/prefilter_FIX.c
opus/silk/fixed/process_gains_FIX.c
opus/silk/fixed/regularize_correlations_FIX.c
opus/silk/fixed/residual_energy16_FIX.c
opus/silk/fixed/residual_energy_FIX.c
opus/silk/fixed/schur64_FIX.c
opus/silk/fixed/schur_FIX.c
opus/silk/fixed/solve_LS_FIX.c
opus/silk/fixed/vector_ops_FIX.c
opus/silk/fixed/warped_autocorrelation_FIX.c
)
else()
target_sources(opus PRIVATE
opus/silk/float/LPC_analysis_filter_FLP.c
opus/silk/float/LPC_inv_pred_gain_FLP.c
opus/silk/float/LTP_analysis_filter_FLP.c
opus/silk/float/LTP_scale_ctrl_FLP.c
opus/silk/float/apply_sine_window_FLP.c
opus/silk/float/autocorrelation_FLP.c
opus/silk/float/burg_modified_FLP.c
opus/silk/float/bwexpander_FLP.c
opus/silk/float/corrMatrix_FLP.c
opus/silk/float/encode_frame_FLP.c
opus/silk/float/energy_FLP.c
opus/silk/float/find_LPC_FLP.c
opus/silk/float/find_LTP_FLP.c
opus/silk/float/find_pitch_lags_FLP.c
opus/silk/float/find_pred_coefs_FLP.c
opus/silk/float/inner_product_FLP.c
opus/silk/float/k2a_FLP.c
opus/silk/float/noise_shape_analysis_FLP.c
opus/silk/float/pitch_analysis_core_FLP.c
opus/silk/float/process_gains_FLP.c
opus/silk/float/regularize_correlations_FLP.c
opus/silk/float/residual_energy_FLP.c
opus/silk/float/scale_copy_vector_FLP.c
opus/silk/float/scale_vector_FLP.c
opus/silk/float/schur_FLP.c
opus/silk/float/sort_FLP.c
opus/silk/float/warped_autocorrelation_FLP.c
opus/silk/float/wrappers_FLP.c
)
endif()
target_compile_definitions(opus PRIVATE OPUS_BUILD ENABLE_HARDENING)
if(NOT MSVC)
if(MINGW)
target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=0)
else()
target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=2)
endif()
endif()
# It is strongly recommended to uncomment one of these VAR_ARRAYS: Use C99
# variable-length arrays for stack allocation USE_ALLOCA: Use alloca() for stack
# allocation If none is defined, then the fallback is a non-threadsafe global
# array
if(OPUS_USE_ALLOCA OR MSVC)
target_compile_definitions(opus PRIVATE USE_ALLOCA)
else()
target_compile_definitions(opus PRIVATE VAR_ARRAYS)
endif()
if(OPUS_CUSTOM_MODES)
target_compile_definitions(opus PRIVATE CUSTOM_MODES)
endif()
if(NOT OPUS_ENABLE_FLOAT_API)
target_compile_definitions(opus PRIVATE DISABLE_FLOAT_API)
endif()
target_compile_definitions(opus
PUBLIC
-DOPUS_VERSION="\\"1.3.1\\""
PRIVATE
# Use C99 intrinsics to speed up float-to-int conversion
HAVE_LRINTF
)
if (FIXED_POINT)
target_compile_definitions(opus PRIVATE -DFIXED_POINT=1 -DDISABLE_FLOAT_API)
endif()
target_include_directories(opus
PUBLIC
opus/include
PRIVATE
opus/celt
opus/silk
opus/silk/fixed
opus/silk/float
opus/src
)

@ -0,0 +1 @@
Subproject commit ad8fe90db79b7d2a135e3dfd2ed6631b0c5662ab

@ -62,6 +62,10 @@ else()
-Wno-unused-parameter -Wno-unused-parameter
) )
if (ARCHITECTURE_x86_64)
add_compile_options("-mcx16")
endif()
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
add_compile_options("-stdlib=libc++") add_compile_options("-stdlib=libc++")
endif() endif()

@ -59,15 +59,24 @@ Stream::State Stream::GetState() const {
return state; return state;
} }
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { s64 Stream::GetBufferReleaseNS(const Buffer& buffer) const {
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
const auto us = const auto ns =
std::chrono::microseconds((static_cast<u64>(num_samples) * 1000000) / sample_rate); std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
return Core::Timing::usToCycles(us); return ns.count();
}
s64 Stream::GetBufferReleaseNSHostTiming(const Buffer& buffer) const {
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
/// DSP signals before playing the last sample, in HLE we emulate this in this way
s64 base_samples = std::max<s64>(static_cast<s64>(num_samples) - 1, 0);
const auto ns =
std::chrono::nanoseconds((static_cast<u64>(base_samples) * 1000000000ULL) / sample_rate);
return ns.count();
} }
static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
const float volume{std::clamp(Settings::values.volume - (1.0f - game_volume), 0.0f, 1.0f)}; const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)};
if (volume == 1.0f) { if (volume == 1.0f) {
return; return;
@ -105,7 +114,11 @@ void Stream::PlayNextBuffer() {
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
core_timing.ScheduleEvent(GetBufferReleaseCycles(*active_buffer), release_event, {}); if (core_timing.IsHostTiming()) {
core_timing.ScheduleEvent(GetBufferReleaseNSHostTiming(*active_buffer), release_event, {});
} else {
core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer), release_event, {});
}
} }
void Stream::ReleaseActiveBuffer() { void Stream::ReleaseActiveBuffer() {

@ -96,7 +96,10 @@ private:
void ReleaseActiveBuffer(); void ReleaseActiveBuffer();
/// Gets the number of core cycles when the specified buffer will be released /// Gets the number of core cycles when the specified buffer will be released
s64 GetBufferReleaseCycles(const Buffer& buffer) const; s64 GetBufferReleaseNS(const Buffer& buffer) const;
/// Gets the number of core cycles when the specified buffer will be released
s64 GetBufferReleaseNSHostTiming(const Buffer& buffer) const;
u32 sample_rate; ///< Sample rate of the stream u32 sample_rate; ///< Sample rate of the stream
Format format; ///< Format of the stream Format format; ///< Format of the stream

@ -98,6 +98,8 @@ add_library(common STATIC
algorithm.h algorithm.h
alignment.h alignment.h
assert.h assert.h
atomic_ops.cpp
atomic_ops.h
detached_tasks.cpp detached_tasks.cpp
detached_tasks.h detached_tasks.h
bit_field.h bit_field.h
@ -110,6 +112,8 @@ add_library(common STATIC
common_types.h common_types.h
dynamic_library.cpp dynamic_library.cpp
dynamic_library.h dynamic_library.h
fiber.cpp
fiber.h
file_util.cpp file_util.cpp
file_util.h file_util.h
hash.h hash.h
@ -143,6 +147,8 @@ add_library(common STATIC
scm_rev.cpp scm_rev.cpp
scm_rev.h scm_rev.h
scope_exit.h scope_exit.h
spin_lock.cpp
spin_lock.h
string_util.cpp string_util.cpp
string_util.h string_util.h
swap.h swap.h
@ -163,6 +169,8 @@ add_library(common STATIC
vector_math.h vector_math.h
virtual_buffer.cpp virtual_buffer.cpp
virtual_buffer.h virtual_buffer.h
wall_clock.cpp
wall_clock.h
web_result.h web_result.h
zstd_compression.cpp zstd_compression.cpp
zstd_compression.h zstd_compression.h
@ -173,12 +181,15 @@ if(ARCHITECTURE_x86_64)
PRIVATE PRIVATE
x64/cpu_detect.cpp x64/cpu_detect.cpp
x64/cpu_detect.h x64/cpu_detect.h
x64/native_clock.cpp
x64/native_clock.h
x64/xbyak_abi.h x64/xbyak_abi.h
x64/xbyak_util.h x64/xbyak_util.h
) )
endif() endif()
create_target_directory_groups(common) create_target_directory_groups(common)
find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
target_link_libraries(common PUBLIC Boost::boost fmt::fmt microprofile) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak) target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak)

@ -0,0 +1,70 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/atomic_ops.h"
#if _MSC_VER
#include <intrin.h>
#endif
namespace Common {
#if _MSC_VER
bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
u8 result = _InterlockedCompareExchange8((char*)pointer, value, expected);
return result == expected;
}
bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
u16 result = _InterlockedCompareExchange16((short*)pointer, value, expected);
return result == expected;
}
bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
u32 result = _InterlockedCompareExchange((long*)pointer, value, expected);
return result == expected;
}
bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
u64 result = _InterlockedCompareExchange64((__int64*)pointer, value, expected);
return result == expected;
}
bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
return _InterlockedCompareExchange128((__int64*)pointer, value[1], value[0],
(__int64*)expected.data()) != 0;
}
#else
bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
return __sync_bool_compare_and_swap(pointer, expected, value);
}
bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
unsigned __int128 value_a;
unsigned __int128 expected_a;
std::memcpy(&value_a, value.data(), sizeof(u128));
std::memcpy(&expected_a, expected.data(), sizeof(u128));
return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
}
#endif
} // namespace Common

@ -0,0 +1,17 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
namespace Common {
bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected);
bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected);
bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected);
bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected);
bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected);
} // namespace Common

@ -0,0 +1,222 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/fiber.h"
#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
#else
#include <boost/context/detail/fcontext.hpp>
#endif
namespace Common {
constexpr std::size_t default_stack_size = 256 * 1024; // 256kb
#if defined(_WIN32) || defined(WIN32)
struct Fiber::FiberImpl {
LPVOID handle = nullptr;
LPVOID rewind_handle = nullptr;
};
void Fiber::Start() {
ASSERT(previous_fiber != nullptr);
previous_fiber->guard.unlock();
previous_fiber.reset();
entry_point(start_parameter);
UNREACHABLE();
}
void Fiber::OnRewind() {
ASSERT(impl->handle != nullptr);
DeleteFiber(impl->handle);
impl->handle = impl->rewind_handle;
impl->rewind_handle = nullptr;
rewind_point(rewind_parameter);
UNREACHABLE();
}
void Fiber::FiberStartFunc(void* fiber_parameter) {
auto fiber = static_cast<Fiber*>(fiber_parameter);
fiber->Start();
}
void Fiber::RewindStartFunc(void* fiber_parameter) {
auto fiber = static_cast<Fiber*>(fiber_parameter);
fiber->OnRewind();
}
Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
: entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
impl = std::make_unique<FiberImpl>();
impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this);
}
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
Fiber::~Fiber() {
if (released) {
return;
}
// Make sure the Fiber is not being used
const bool locked = guard.try_lock();
ASSERT_MSG(locked, "Destroying a fiber that's still running");
if (locked) {
guard.unlock();
}
DeleteFiber(impl->handle);
}
void Fiber::Exit() {
ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
if (!is_thread_fiber) {
return;
}
ConvertFiberToThread();
guard.unlock();
released = true;
}
void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
rewind_point = std::move(rewind_func);
rewind_parameter = start_parameter;
}
void Fiber::Rewind() {
ASSERT(rewind_point);
ASSERT(impl->rewind_handle == nullptr);
impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this);
SwitchToFiber(impl->rewind_handle);
}
void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
ASSERT_MSG(to != nullptr, "Next fiber is null!");
to->guard.lock();
to->previous_fiber = from;
SwitchToFiber(to->impl->handle);
ASSERT(from->previous_fiber != nullptr);
from->previous_fiber->guard.unlock();
from->previous_fiber.reset();
}
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
fiber->guard.lock();
fiber->impl->handle = ConvertThreadToFiber(nullptr);
fiber->is_thread_fiber = true;
return fiber;
}
#else
struct Fiber::FiberImpl {
alignas(64) std::array<u8, default_stack_size> stack;
alignas(64) std::array<u8, default_stack_size> rewind_stack;
u8* stack_limit;
u8* rewind_stack_limit;
boost::context::detail::fcontext_t context;
boost::context::detail::fcontext_t rewind_context;
};
void Fiber::Start(boost::context::detail::transfer_t& transfer) {
ASSERT(previous_fiber != nullptr);
previous_fiber->impl->context = transfer.fctx;
previous_fiber->guard.unlock();
previous_fiber.reset();
entry_point(start_parameter);
UNREACHABLE();
}
void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) {
ASSERT(impl->context != nullptr);
impl->context = impl->rewind_context;
impl->rewind_context = nullptr;
u8* tmp = impl->stack_limit;
impl->stack_limit = impl->rewind_stack_limit;
impl->rewind_stack_limit = tmp;
rewind_point(rewind_parameter);
UNREACHABLE();
}
void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) {
auto fiber = static_cast<Fiber*>(transfer.data);
fiber->Start(transfer);
}
void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
auto fiber = static_cast<Fiber*>(transfer.data);
fiber->OnRewind(transfer);
}
Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
: entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
impl = std::make_unique<FiberImpl>();
impl->stack_limit = impl->stack.data();
impl->rewind_stack_limit = impl->rewind_stack.data();
u8* stack_base = impl->stack_limit + default_stack_size;
impl->context =
boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc);
}
void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
rewind_point = std::move(rewind_func);
rewind_parameter = start_parameter;
}
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
Fiber::~Fiber() {
if (released) {
return;
}
// Make sure the Fiber is not being used
const bool locked = guard.try_lock();
ASSERT_MSG(locked, "Destroying a fiber that's still running");
if (locked) {
guard.unlock();
}
}
void Fiber::Exit() {
ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
if (!is_thread_fiber) {
return;
}
guard.unlock();
released = true;
}
void Fiber::Rewind() {
ASSERT(rewind_point);
ASSERT(impl->rewind_context == nullptr);
u8* stack_base = impl->rewind_stack_limit + default_stack_size;
impl->rewind_context =
boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc);
boost::context::detail::jump_fcontext(impl->rewind_context, this);
}
void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
ASSERT_MSG(to != nullptr, "Next fiber is null!");
to->guard.lock();
to->previous_fiber = from;
auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get());
ASSERT(from->previous_fiber != nullptr);
from->previous_fiber->impl->context = transfer.fctx;
from->previous_fiber->guard.unlock();
from->previous_fiber.reset();
}
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
fiber->guard.lock();
fiber->is_thread_fiber = true;
return fiber;
}
#endif
} // namespace Common

@ -0,0 +1,92 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include "common/common_types.h"
#include "common/spin_lock.h"
#if !defined(_WIN32) && !defined(WIN32)
namespace boost::context::detail {
struct transfer_t;
}
#endif
namespace Common {
/**
* Fiber class
* a fiber is a userspace thread with it's own context. They can be used to
* implement coroutines, emulated threading systems and certain asynchronous
* patterns.
*
* This class implements fibers at a low level, thus allowing greater freedom
* to implement such patterns. This fiber class is 'threadsafe' only one fiber
* can be running at a time and threads will be locked while trying to yield to
* a running fiber until it yields. WARNING exchanging two running fibers between
* threads will cause a deadlock. In order to prevent a deadlock, each thread should
* have an intermediary fiber, you switch to the intermediary fiber of the current
* thread and then from it switch to the expected fiber. This way you can exchange
* 2 fibers within 2 different threads.
*/
class Fiber {
public:
Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter);
~Fiber();
Fiber(const Fiber&) = delete;
Fiber& operator=(const Fiber&) = delete;
Fiber(Fiber&&) = default;
Fiber& operator=(Fiber&&) = default;
/// Yields control from Fiber 'from' to Fiber 'to'
/// Fiber 'from' must be the currently running fiber.
static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to);
static std::shared_ptr<Fiber> ThreadToFiber();
void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter);
void Rewind();
/// Only call from main thread's fiber
void Exit();
/// Changes the start parameter of the fiber. Has no effect if the fiber already started
void SetStartParameter(void* new_parameter) {
start_parameter = new_parameter;
}
private:
Fiber();
#if defined(_WIN32) || defined(WIN32)
void OnRewind();
void Start();
static void FiberStartFunc(void* fiber_parameter);
static void RewindStartFunc(void* fiber_parameter);
#else
void OnRewind(boost::context::detail::transfer_t& transfer);
void Start(boost::context::detail::transfer_t& transfer);
static void FiberStartFunc(boost::context::detail::transfer_t transfer);
static void RewindStartFunc(boost::context::detail::transfer_t transfer);
#endif
struct FiberImpl;
SpinLock guard{};
std::function<void(void*)> entry_point;
std::function<void(void*)> rewind_point;
void* rewind_parameter{};
void* start_parameter{};
std::shared_ptr<Fiber> previous_fiber;
std::unique_ptr<FiberImpl> impl;
bool is_thread_fiber{};
bool released{};
};
} // namespace Common

@ -9,10 +9,12 @@
// clang-format on // clang-format on
#else #else
#include <sys/types.h> #include <sys/types.h>
#ifdef __APPLE__ #if defined(__APPLE__) || defined(__FreeBSD__)
#include <sys/sysctl.h> #include <sys/sysctl.h>
#else #elif defined(__linux__)
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#else
#include <unistd.h>
#endif #endif
#endif #endif
@ -38,15 +40,26 @@ static MemoryInfo Detect() {
// hw and vm are defined in sysctl.h // hw and vm are defined in sysctl.h
// https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471 // https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471
// sysctlbyname(const char *, void *, size_t *, void *, size_t); // sysctlbyname(const char *, void *, size_t *, void *, size_t);
sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, NULL, 0); sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, nullptr, 0);
sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, NULL, 0); sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, nullptr, 0);
mem_info.TotalPhysicalMemory = ramsize; mem_info.TotalPhysicalMemory = ramsize;
mem_info.TotalSwapMemory = vmusage.xsu_total; mem_info.TotalSwapMemory = vmusage.xsu_total;
#else #elif defined(__FreeBSD__)
u_long physmem, swap_total;
std::size_t sizeof_u_long = sizeof(u_long);
// sysctlbyname(const char *, void *, size_t *, const void *, size_t);
sysctlbyname("hw.physmem", &physmem, &sizeof_u_long, nullptr, 0);
sysctlbyname("vm.swap_total", &swap_total, &sizeof_u_long, nullptr, 0);
mem_info.TotalPhysicalMemory = physmem;
mem_info.TotalSwapMemory = swap_total;
#elif defined(__linux__)
struct sysinfo meminfo; struct sysinfo meminfo;
sysinfo(&meminfo); sysinfo(&meminfo);
mem_info.TotalPhysicalMemory = meminfo.totalram; mem_info.TotalPhysicalMemory = meminfo.totalram;
mem_info.TotalSwapMemory = meminfo.totalswap; mem_info.TotalSwapMemory = meminfo.totalswap;
#else
mem_info.TotalPhysicalMemory = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
mem_info.TotalSwapMemory = 0;
#endif #endif
return mem_info; return mem_info;

@ -0,0 +1,54 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/spin_lock.h"
#if _MSC_VER
#include <intrin.h>
#if _M_AMD64
#define __x86_64__ 1
#endif
#if _M_ARM64
#define __aarch64__ 1
#endif
#else
#if __x86_64__
#include <xmmintrin.h>
#endif
#endif
namespace {
void ThreadPause() {
#if __x86_64__
_mm_pause();
#elif __aarch64__ && _MSC_VER
__yield();
#elif __aarch64__
asm("yield");
#endif
}
} // Anonymous namespace
namespace Common {
void SpinLock::lock() {
while (lck.test_and_set(std::memory_order_acquire)) {
ThreadPause();
}
}
void SpinLock::unlock() {
lck.clear(std::memory_order_release);
}
bool SpinLock::try_lock() {
if (lck.test_and_set(std::memory_order_acquire)) {
return false;
}
return true;
}
} // namespace Common

@ -0,0 +1,26 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
namespace Common {
/**
* SpinLock class
* a lock similar to mutex that forces a thread to spin wait instead calling the
* supervisor. Should be used on short sequences of code.
*/
class SpinLock {
public:
void lock();
void unlock();
bool try_lock();
private:
std::atomic_flag lck = ATOMIC_FLAG_INIT;
};
} // namespace Common

@ -60,6 +60,7 @@ void AppendCPUInfo(FieldCollection& fc) {
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX512", Common::GetCPUCaps().avx512);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma); fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);

@ -25,6 +25,52 @@
namespace Common { namespace Common {
#ifdef _WIN32
void SetCurrentThreadPriority(ThreadPriority new_priority) {
auto handle = GetCurrentThread();
int windows_priority = 0;
switch (new_priority) {
case ThreadPriority::Low:
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
break;
case ThreadPriority::Normal:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
case ThreadPriority::High:
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case ThreadPriority::VeryHigh:
windows_priority = THREAD_PRIORITY_HIGHEST;
break;
default:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
}
SetThreadPriority(handle, windows_priority);
}
#else
void SetCurrentThreadPriority(ThreadPriority new_priority) {
pthread_t this_thread = pthread_self();
s32 max_prio = sched_get_priority_max(SCHED_OTHER);
s32 min_prio = sched_get_priority_min(SCHED_OTHER);
u32 level = static_cast<u32>(new_priority) + 1;
struct sched_param params;
if (max_prio > min_prio) {
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
} else {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
}
pthread_setschedparam(this_thread, SCHED_OTHER, &params);
}
#endif
#ifdef _MSC_VER #ifdef _MSC_VER
// Sets the debugger-visible name of the current thread. // Sets the debugger-visible name of the current thread.
@ -70,6 +116,12 @@ void SetCurrentThreadName(const char* name) {
} }
#endif #endif
#if defined(_WIN32)
void SetCurrentThreadName(const char* name) {
// Do Nothing on MingW
}
#endif
#endif #endif
} // namespace Common } // namespace Common

@ -9,6 +9,7 @@
#include <cstddef> #include <cstddef>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include "common/common_types.h"
namespace Common { namespace Common {
@ -28,8 +29,7 @@ public:
is_set = false; is_set = false;
} }
template <class Duration> bool WaitFor(const std::chrono::nanoseconds& time) {
bool WaitFor(const std::chrono::duration<Duration>& time) {
std::unique_lock lk{mutex}; std::unique_lock lk{mutex};
if (!condvar.wait_for(lk, time, [this] { return is_set; })) if (!condvar.wait_for(lk, time, [this] { return is_set; }))
return false; return false;
@ -86,6 +86,15 @@ private:
std::size_t generation = 0; // Incremented once each time the barrier is used std::size_t generation = 0; // Incremented once each time the barrier is used
}; };
enum class ThreadPriority : u32 {
Low = 0,
Normal = 1,
High = 2,
VeryHigh = 3,
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name); void SetCurrentThreadName(const char* name);
} // namespace Common } // namespace Common

@ -6,12 +6,38 @@
#include <intrin.h> #include <intrin.h>
#pragma intrinsic(_umul128) #pragma intrinsic(_umul128)
#pragma intrinsic(_udiv128)
#endif #endif
#include <cstring> #include <cstring>
#include "common/uint128.h" #include "common/uint128.h"
namespace Common { namespace Common {
#ifdef _MSC_VER
u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
u128 r{};
r[0] = _umul128(a, b, &r[1]);
u64 remainder;
#if _MSC_VER < 1923
return udiv128(r[1], r[0], d, &remainder);
#else
return _udiv128(r[1], r[0], d, &remainder);
#endif
}
#else
u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
const u64 diva = a / d;
const u64 moda = a % d;
const u64 divb = b / d;
const u64 modb = b % d;
return diva * b + moda * divb + moda * modb / d;
}
#endif
u128 Multiply64Into128(u64 a, u64 b) { u128 Multiply64Into128(u64 a, u64 b) {
u128 result; u128 result;
#ifdef _MSC_VER #ifdef _MSC_VER

@ -9,6 +9,9 @@
namespace Common { namespace Common {
// This function multiplies 2 u64 values and divides it by a u64 value.
u64 MultiplyAndDivide64(u64 a, u64 b, u64 d);
// This function multiplies 2 u64 values and produces a u128 value; // This function multiplies 2 u64 values and produces a u128 value;
u128 Multiply64Into128(u64 a, u64 b); u128 Multiply64Into128(u64 a, u64 b);

@ -0,0 +1,91 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/uint128.h"
#include "common/wall_clock.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#include "common/x64/native_clock.h"
#endif
namespace Common {
using base_timer = std::chrono::steady_clock;
using base_time_point = std::chrono::time_point<base_timer>;
class StandardWallClock : public WallClock {
public:
StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency)
: WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) {
start_time = base_timer::now();
}
std::chrono::nanoseconds GetTimeNS() override {
base_time_point current = base_timer::now();
auto elapsed = current - start_time;
return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
}
std::chrono::microseconds GetTimeUS() override {
base_time_point current = base_timer::now();
auto elapsed = current - start_time;
return std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
}
std::chrono::milliseconds GetTimeMS() override {
base_time_point current = base_timer::now();
auto elapsed = current - start_time;
return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
}
u64 GetClockCycles() override {
std::chrono::nanoseconds time_now = GetTimeNS();
const u128 temporary =
Common::Multiply64Into128(time_now.count(), emulated_clock_frequency);
return Common::Divide128On32(temporary, 1000000000).first;
}
u64 GetCPUCycles() override {
std::chrono::nanoseconds time_now = GetTimeNS();
const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency);
return Common::Divide128On32(temporary, 1000000000).first;
}
void Pause(bool is_paused) override {
// Do nothing in this clock type.
}
private:
base_time_point start_time;
};
#ifdef ARCHITECTURE_x86_64
std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
u32 emulated_clock_frequency) {
const auto& caps = GetCPUCaps();
u64 rtsc_frequency = 0;
if (caps.invariant_tsc) {
rtsc_frequency = EstimateRDTSCFrequency();
}
if (rtsc_frequency == 0) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
emulated_clock_frequency);
} else {
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
rtsc_frequency);
}
}
#else
std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
u32 emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
}
#endif
} // namespace Common

@ -0,0 +1,53 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <memory>
#include "common/common_types.h"
namespace Common {
class WallClock {
public:
/// Returns current wall time in nanoseconds
virtual std::chrono::nanoseconds GetTimeNS() = 0;
/// Returns current wall time in microseconds
virtual std::chrono::microseconds GetTimeUS() = 0;
/// Returns current wall time in milliseconds
virtual std::chrono::milliseconds GetTimeMS() = 0;
/// Returns current wall time in emulated clock cycles
virtual u64 GetClockCycles() = 0;
/// Returns current wall time in emulated cpu cycles
virtual u64 GetCPUCycles() = 0;
virtual void Pause(bool is_paused) = 0;
/// Tells if the wall clock, uses the host CPU's hardware clock
bool IsNative() const {
return is_native;
}
protected:
WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native)
: emulated_cpu_frequency{emulated_cpu_frequency},
emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {}
u64 emulated_cpu_frequency;
u64 emulated_clock_frequency;
private:
bool is_native;
};
std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
u32 emulated_clock_frequency);
} // namespace Common

@ -62,6 +62,17 @@ static CPUCaps Detect() {
std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int)); std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int));
std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int)); std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int));
std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int)); std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int));
if (cpu_id[1] == 0x756e6547 && cpu_id[2] == 0x6c65746e && cpu_id[3] == 0x49656e69)
caps.manufacturer = Manufacturer::Intel;
else if (cpu_id[1] == 0x68747541 && cpu_id[2] == 0x444d4163 && cpu_id[3] == 0x69746e65)
caps.manufacturer = Manufacturer::AMD;
else if (cpu_id[1] == 0x6f677948 && cpu_id[2] == 0x656e6975 && cpu_id[3] == 0x6e65476e)
caps.manufacturer = Manufacturer::Hygon;
else
caps.manufacturer = Manufacturer::Unknown;
u32 family = {};
u32 model = {};
__cpuid(cpu_id, 0x80000000); __cpuid(cpu_id, 0x80000000);
@ -73,6 +84,14 @@ 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;
@ -110,6 +129,11 @@ static CPUCaps Detect() {
caps.bmi1 = true; caps.bmi1 = true;
if ((cpu_id[1] >> 8) & 1) if ((cpu_id[1] >> 8) & 1)
caps.bmi2 = true; caps.bmi2 = true;
// Checks for AVX512F, AVX512CD, AVX512VL, AVX512DQ, AVX512BW (Intel Skylake-X/SP)
if ((cpu_id[1] >> 16) & 1 && (cpu_id[1] >> 28) & 1 && (cpu_id[1] >> 31) & 1 &&
(cpu_id[1] >> 17) & 1 && (cpu_id[1] >> 30) & 1) {
caps.avx512 = caps.avx2;
}
} }
} }
@ -130,6 +154,20 @@ static CPUCaps Detect() {
caps.fma4 = true; caps.fma4 = true;
} }
if (max_ex_fn >= 0x80000007) {
__cpuid(cpu_id, 0x80000007);
if (cpu_id[3] & (1 << 8)) {
caps.invariant_tsc = true;
}
}
if (max_std_fn >= 0x16) {
__cpuid(cpu_id, 0x16);
caps.base_frequency = cpu_id[0];
caps.max_frequency = cpu_id[1];
caps.bus_frequency = cpu_id[2];
}
return caps; return caps;
} }

@ -6,8 +6,16 @@
namespace Common { namespace Common {
enum class Manufacturer : u32 {
Intel = 0,
AMD = 1,
Hygon = 2,
Unknown = 3,
};
/// x86/x64 CPU capabilities that may be detected by this module /// x86/x64 CPU capabilities that may be detected by this module
struct CPUCaps { struct CPUCaps {
Manufacturer manufacturer;
char cpu_string[0x21]; char cpu_string[0x21];
char brand_string[0x41]; char brand_string[0x41];
bool sse; bool sse;
@ -19,11 +27,16 @@ struct CPUCaps {
bool lzcnt; bool lzcnt;
bool avx; bool avx;
bool avx2; bool avx2;
bool avx512;
bool bmi1; bool bmi1;
bool bmi2; bool bmi2;
bool fma; bool fma;
bool fma4; bool fma4;
bool aes; bool aes;
bool invariant_tsc;
u32 base_frequency;
u32 max_frequency;
u32 bus_frequency;
}; };
/** /**

@ -0,0 +1,103 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <mutex>
#include <thread>
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#include "common/uint128.h"
#include "common/x64/native_clock.h"
namespace Common {
u64 EstimateRDTSCFrequency() {
const auto milli_10 = std::chrono::milliseconds{10};
// get current time
_mm_mfence();
const u64 tscStart = __rdtsc();
const auto startTime = std::chrono::high_resolution_clock::now();
// 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();
const u64 tscEnd = __rdtsc();
// calculate difference
const u64 timer_diff =
std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
const u64 tsc_diff = tscEnd - tscStart;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
return tsc_freq;
}
namespace X64 {
NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency,
u64 rtsc_frequency)
: WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{
rtsc_frequency} {
_mm_mfence();
last_measure = __rdtsc();
accumulated_ticks = 0U;
}
u64 NativeClock::GetRTSC() {
std::scoped_lock scope{rtsc_serialize};
_mm_mfence();
const u64 current_measure = __rdtsc();
u64 diff = current_measure - last_measure;
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
if (current_measure > last_measure) {
last_measure = current_measure;
}
accumulated_ticks += diff;
/// The clock cannot be more precise than the guest timer, remove the lower bits
return accumulated_ticks & inaccuracy_mask;
}
void NativeClock::Pause(bool is_paused) {
if (!is_paused) {
_mm_mfence();
last_measure = __rdtsc();
}
}
std::chrono::nanoseconds NativeClock::GetTimeNS() {
const u64 rtsc_value = GetRTSC();
return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)};
}
std::chrono::microseconds NativeClock::GetTimeUS() {
const u64 rtsc_value = GetRTSC();
return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() {
const u64 rtsc_value = GetRTSC();
return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)};
}
u64 NativeClock::GetClockCycles() {
const u64 rtsc_value = GetRTSC();
return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency);
}
u64 NativeClock::GetCPUCycles() {
const u64 rtsc_value = GetRTSC();
return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency);
}
} // namespace X64
} // namespace Common

@ -0,0 +1,48 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <optional>
#include "common/spin_lock.h"
#include "common/wall_clock.h"
namespace Common {
namespace X64 {
class NativeClock : public WallClock {
public:
NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency);
std::chrono::nanoseconds GetTimeNS() override;
std::chrono::microseconds GetTimeUS() override;
std::chrono::milliseconds GetTimeMS() override;
u64 GetClockCycles() override;
u64 GetCPUCycles() override;
void Pause(bool is_paused) override;
private:
u64 GetRTSC();
/// value used to reduce the native clocks accuracy as some apss rely on
/// undefined behavior where the level of accuracy in the clock shouldn't
/// be higher.
static constexpr u64 inaccuracy_mask = ~(0x400 - 1);
SpinLock rtsc_serialize{};
u64 last_measure{};
u64 accumulated_ticks{};
u64 rtsc_frequency;
};
} // namespace X64
u64 EstimateRDTSCFrequency();
} // namespace Common

@ -7,6 +7,16 @@ endif()
add_library(core STATIC add_library(core STATIC
arm/arm_interface.h arm/arm_interface.h
arm/arm_interface.cpp arm/arm_interface.cpp
arm/cpu_interrupt_handler.cpp
arm/cpu_interrupt_handler.h
arm/dynarmic/arm_dynarmic_32.cpp
arm/dynarmic/arm_dynarmic_32.h
arm/dynarmic/arm_dynarmic_64.cpp
arm/dynarmic/arm_dynarmic_64.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
arm/dynarmic/arm_exclusive_monitor.cpp
arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp arm/exclusive_monitor.cpp
arm/exclusive_monitor.h arm/exclusive_monitor.h
arm/unicorn/arm_unicorn.cpp arm/unicorn/arm_unicorn.cpp
@ -15,8 +25,6 @@ add_library(core STATIC
constants.h constants.h
core.cpp core.cpp
core.h core.h
core_manager.cpp
core_manager.h
core_timing.cpp core_timing.cpp
core_timing.h core_timing.h
core_timing_util.cpp core_timing_util.cpp
@ -606,7 +614,7 @@ endif()
create_target_directory_groups(core) create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus unicorn zip) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip)
if (YUZU_ENABLE_BOXCAT) if (YUZU_ENABLE_BOXCAT)
target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT) target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT)

@ -139,6 +139,63 @@ std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_addr
constexpr u64 SEGMENT_BASE = 0x7100000000ull; constexpr u64 SEGMENT_BASE = 0x7100000000ull;
std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
System& system, const ThreadContext64& ctx) {
std::vector<BacktraceEntry> out;
auto& memory = system.Memory();
auto fp = ctx.cpu_registers[29];
auto lr = ctx.cpu_registers[30];
while (true) {
out.push_back({"", 0, lr, 0});
if (!fp) {
break;
}
lr = memory.Read64(fp + 8) - 4;
fp = memory.Read64(fp);
}
std::map<VAddr, std::string> modules;
auto& loader{system.GetAppLoader()};
if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
return {};
}
std::map<std::string, Symbols> symbols;
for (const auto& module : modules) {
symbols.insert_or_assign(module.second, GetSymbols(module.first, memory));
}
for (auto& entry : out) {
VAddr base = 0;
for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) {
const auto& module{*iter};
if (entry.original_address >= module.first) {
entry.module = module.second;
base = module.first;
break;
}
}
entry.offset = entry.original_address - base;
entry.address = SEGMENT_BASE + entry.offset;
if (entry.module.empty())
entry.module = "unknown";
const auto symbol_set = symbols.find(entry.module);
if (symbol_set != symbols.end()) {
const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
if (symbol.has_value()) {
// TODO(DarkLordZach): Add demangling of symbol names.
entry.name = *symbol;
}
}
}
return out;
}
std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const { std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
std::vector<BacktraceEntry> out; std::vector<BacktraceEntry> out;
auto& memory = system.Memory(); auto& memory = system.Memory();

@ -7,6 +7,7 @@
#include <array> #include <array>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hardware_properties.h"
namespace Common { namespace Common {
struct PageTable; struct PageTable;
@ -18,25 +19,29 @@ enum class VMAPermission : u8;
namespace Core { namespace Core {
class System; class System;
class CPUInterruptHandler;
using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
/// Generic ARMv8 CPU interface /// Generic ARMv8 CPU interface
class ARM_Interface : NonCopyable { class ARM_Interface : NonCopyable {
public: public:
explicit ARM_Interface(System& system_) : system{system_} {} explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers, bool uses_wall_clock)
: system{system_}, interrupt_handlers{interrupt_handlers}, uses_wall_clock{
uses_wall_clock} {}
virtual ~ARM_Interface() = default; virtual ~ARM_Interface() = default;
struct ThreadContext32 { struct ThreadContext32 {
std::array<u32, 16> cpu_registers{}; std::array<u32, 16> cpu_registers{};
std::array<u32, 64> extension_registers{};
u32 cpsr{}; u32 cpsr{};
std::array<u8, 4> padding{};
std::array<u64, 32> fprs{};
u32 fpscr{}; u32 fpscr{};
u32 fpexc{}; u32 fpexc{};
u32 tpidr{}; u32 tpidr{};
}; };
// Internally within the kernel, it expects the AArch32 version of the // Internally within the kernel, it expects the AArch32 version of the
// thread context to be 344 bytes in size. // thread context to be 344 bytes in size.
static_assert(sizeof(ThreadContext32) == 0x158); static_assert(sizeof(ThreadContext32) == 0x150);
struct ThreadContext64 { struct ThreadContext64 {
std::array<u64, 31> cpu_registers{}; std::array<u64, 31> cpu_registers{};
@ -143,6 +148,8 @@ public:
*/ */
virtual void SetTPIDR_EL0(u64 value) = 0; virtual void SetTPIDR_EL0(u64 value) = 0;
virtual void ChangeProcessorID(std::size_t new_core_id) = 0;
virtual void SaveContext(ThreadContext32& ctx) = 0; virtual void SaveContext(ThreadContext32& ctx) = 0;
virtual void SaveContext(ThreadContext64& ctx) = 0; virtual void SaveContext(ThreadContext64& ctx) = 0;
virtual void LoadContext(const ThreadContext32& ctx) = 0; virtual void LoadContext(const ThreadContext32& ctx) = 0;
@ -162,6 +169,9 @@ public:
std::string name; std::string name;
}; };
static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
const ThreadContext64& ctx);
std::vector<BacktraceEntry> GetBacktrace() const; std::vector<BacktraceEntry> GetBacktrace() const;
/// fp (= r29) points to the last frame record. /// fp (= r29) points to the last frame record.
@ -175,6 +185,8 @@ public:
protected: protected:
/// System context that this ARM interface is running under. /// System context that this ARM interface is running under.
System& system; System& system;
CPUInterrupts& interrupt_handlers;
bool uses_wall_clock;
}; };
} // namespace Core } // namespace Core

@ -0,0 +1,29 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/thread.h"
#include "core/arm/cpu_interrupt_handler.h"
namespace Core {
CPUInterruptHandler::CPUInterruptHandler() : is_interrupted{} {
interrupt_event = std::make_unique<Common::Event>();
}
CPUInterruptHandler::~CPUInterruptHandler() = default;
void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
if (is_interrupted_) {
interrupt_event->Set();
}
this->is_interrupted = is_interrupted_;
}
void CPUInterruptHandler::AwaitInterrupt() {
interrupt_event->Wait();
}
} // namespace Core

@ -0,0 +1,39 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
namespace Common {
class Event;
}
namespace Core {
class CPUInterruptHandler {
public:
CPUInterruptHandler();
~CPUInterruptHandler();
CPUInterruptHandler(const CPUInterruptHandler&) = delete;
CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete;
CPUInterruptHandler(CPUInterruptHandler&&) = default;
CPUInterruptHandler& operator=(CPUInterruptHandler&&) = default;
bool IsInterrupted() const {
return is_interrupted;
}
void SetInterrupt(bool is_interrupted);
void AwaitInterrupt();
private:
bool is_interrupted{};
std::unique_ptr<Common::Event> interrupt_event;
};
} // namespace Core

@ -7,15 +7,17 @@
#include <dynarmic/A32/a32.h> #include <dynarmic/A32/a32.h>
#include <dynarmic/A32/config.h> #include <dynarmic/A32/config.h>
#include <dynarmic/A32/context.h> #include <dynarmic/A32/context.h>
#include "common/microprofile.h" #include "common/logging/log.h"
#include "common/page_table.h"
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#include "core/arm/dynarmic/arm_dynarmic_cp15.h" #include "core/arm/dynarmic/arm_dynarmic_cp15.h"
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/settings.h"
namespace Core { namespace Core {
@ -49,6 +51,19 @@ public:
parent.system.Memory().Write64(vaddr, value); parent.system.Memory().Write64(vaddr, value);
} }
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
return parent.system.Memory().WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
return parent.system.Memory().WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
return parent.system.Memory().WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
return parent.system.Memory().WriteExclusive64(vaddr, value, expected);
}
void InterpreterFallback(u32 pc, std::size_t num_instructions) override { void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
UNIMPLEMENTED_MSG("This should never happen, pc = {:08X}, code = {:08X}", pc, UNIMPLEMENTED_MSG("This should never happen, pc = {:08X}, code = {:08X}", pc,
MemoryReadCode(pc)); MemoryReadCode(pc));
@ -62,7 +77,7 @@ public:
case Dynarmic::A32::Exception::Breakpoint: case Dynarmic::A32::Exception::Breakpoint:
break; break;
} }
LOG_CRITICAL(HW_GPU, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc)); static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
UNIMPLEMENTED(); UNIMPLEMENTED();
} }
@ -72,24 +87,36 @@ public:
} }
void AddTicks(u64 ticks) override { void AddTicks(u64 ticks) override {
if (parent.uses_wall_clock) {
return;
}
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off // rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should // if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4 // device a way so that timing is consistent across all cores without increasing the ticks 4
// times. // times.
u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES; u64 amortized_ticks =
(ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick. // Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1); amortized_ticks = std::max<u64>(amortized_ticks, 1);
parent.system.CoreTiming().AddTicks(amortized_ticks); parent.system.CoreTiming().AddTicks(amortized_ticks);
num_interpreted_instructions = 0; num_interpreted_instructions = 0;
} }
u64 GetTicksRemaining() override { u64 GetTicksRemaining() override {
return std::max(parent.system.CoreTiming().GetDowncount(), {}); if (parent.uses_wall_clock) {
if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
return minimum_run_cycles;
}
return 0U;
}
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
} }
ARM_Dynarmic_32& parent; ARM_Dynarmic_32& parent;
std::size_t num_interpreted_instructions{}; std::size_t num_interpreted_instructions{};
static constexpr u64 minimum_run_cycles = 1000U;
}; };
std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& page_table, std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& page_table,
@ -100,13 +127,31 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
// config.page_table = &page_table.pointers; // config.page_table = &page_table.pointers;
config.coprocessors[15] = cp15; config.coprocessors[15] = cp15;
config.define_unpredictable_behaviour = true; config.define_unpredictable_behaviour = true;
static constexpr std::size_t PAGE_BITS = 12;
static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - PAGE_BITS);
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
page_table.pointers.data());
config.absolute_offset_page_table = true;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
// Multi-process state
config.processor_id = core_index;
config.global_monitor = &exclusive_monitor.monitor;
// Timing
config.wall_clock_cntpct = uses_wall_clock;
// Optimizations
if (Settings::values.disable_cpu_opt) {
config.enable_optimizations = false;
config.enable_fast_dispatch = false;
}
return std::make_unique<Dynarmic::A32::Jit>(config); return std::make_unique<Dynarmic::A32::Jit>(config);
} }
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_32, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
void ARM_Dynarmic_32::Run() { void ARM_Dynarmic_32::Run() {
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic_32);
jit->Run(); jit->Run();
} }
@ -114,9 +159,11 @@ void ARM_Dynarmic_32::Step() {
jit->Step(); jit->Step();
} }
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor, ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, CPUInterrupts& interrupt_handlers,
bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
std::size_t core_index) std::size_t core_index)
: ARM_Interface{system}, cb(std::make_unique<DynarmicCallbacks32>(*this)), : ARM_Interface{system, interrupt_handlers, uses_wall_clock},
cb(std::make_unique<DynarmicCallbacks32>(*this)),
cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index}, cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index},
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {} exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
@ -168,17 +215,25 @@ void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) {
cp15->uprw = static_cast<u32>(value); cp15->uprw = static_cast<u32>(value);
} }
void ARM_Dynarmic_32::ChangeProcessorID(std::size_t new_core_id) {
jit->ChangeProcessorID(new_core_id);
}
void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) { void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
Dynarmic::A32::Context context; Dynarmic::A32::Context context;
jit->SaveContext(context); jit->SaveContext(context);
ctx.cpu_registers = context.Regs(); ctx.cpu_registers = context.Regs();
ctx.extension_registers = context.ExtRegs();
ctx.cpsr = context.Cpsr(); ctx.cpsr = context.Cpsr();
ctx.fpscr = context.Fpscr();
} }
void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) { void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) {
Dynarmic::A32::Context context; Dynarmic::A32::Context context;
context.Regs() = ctx.cpu_registers; context.Regs() = ctx.cpu_registers;
context.ExtRegs() = ctx.extension_registers;
context.SetCpsr(ctx.cpsr); context.SetCpsr(ctx.cpsr);
context.SetFpscr(ctx.fpscr);
jit->LoadContext(context); jit->LoadContext(context);
} }
@ -187,10 +242,15 @@ void ARM_Dynarmic_32::PrepareReschedule() {
} }
void ARM_Dynarmic_32::ClearInstructionCache() { void ARM_Dynarmic_32::ClearInstructionCache() {
if (!jit) {
return;
}
jit->ClearCache(); jit->ClearCache();
} }
void ARM_Dynarmic_32::ClearExclusiveState() {} void ARM_Dynarmic_32::ClearExclusiveState() {
jit->ClearExclusiveState();
}
void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table, void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table,
std::size_t new_address_space_size_in_bits) { std::size_t new_address_space_size_in_bits) {

@ -9,7 +9,7 @@
#include <dynarmic/A32/a32.h> #include <dynarmic/A32/a32.h>
#include <dynarmic/A64/a64.h> #include <dynarmic/A64/a64.h>
#include <dynarmic/A64/exclusive_monitor.h> #include <dynarmic/exclusive_monitor.h>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/hash.h" #include "common/hash.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
@ -21,6 +21,7 @@ class Memory;
namespace Core { namespace Core {
class CPUInterruptHandler;
class DynarmicCallbacks32; class DynarmicCallbacks32;
class DynarmicCP15; class DynarmicCP15;
class DynarmicExclusiveMonitor; class DynarmicExclusiveMonitor;
@ -28,7 +29,8 @@ class System;
class ARM_Dynarmic_32 final : public ARM_Interface { class ARM_Dynarmic_32 final : public ARM_Interface {
public: public:
ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index); ARM_Dynarmic_32(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic_32() override; ~ARM_Dynarmic_32() override;
void SetPC(u64 pc) override; void SetPC(u64 pc) override;
@ -45,6 +47,7 @@ public:
void SetTlsAddress(VAddr address) override; void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override; void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override; u64 GetTPIDR_EL0() const override;
void ChangeProcessorID(std::size_t new_core_id) override;
void SaveContext(ThreadContext32& ctx) override; void SaveContext(ThreadContext32& ctx) override;
void SaveContext(ThreadContext64& ctx) override {} void SaveContext(ThreadContext64& ctx) override {}

@ -7,11 +7,11 @@
#include <dynarmic/A64/a64.h> #include <dynarmic/A64/a64.h>
#include <dynarmic/A64/config.h> #include <dynarmic/A64/config.h>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/page_table.h" #include "common/page_table.h"
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h" #include "core/arm/dynarmic/arm_dynarmic_64.h"
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/gdbstub/gdbstub.h" #include "core/gdbstub/gdbstub.h"
@ -65,6 +65,22 @@ public:
memory.Write64(vaddr + 8, value[1]); memory.Write64(vaddr + 8, value[1]);
} }
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
return parent.system.Memory().WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
return parent.system.Memory().WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
return parent.system.Memory().WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
return parent.system.Memory().WriteExclusive64(vaddr, value, expected);
}
bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
return parent.system.Memory().WriteExclusive128(vaddr, value, expected);
}
void InterpreterFallback(u64 pc, std::size_t num_instructions) override { void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc, LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
num_instructions, MemoryReadCode(pc)); num_instructions, MemoryReadCode(pc));
@ -98,8 +114,8 @@ public:
} }
[[fallthrough]]; [[fallthrough]];
default: default:
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})", ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
static_cast<std::size_t>(exception), pc); static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
} }
} }
@ -108,29 +124,42 @@ public:
} }
void AddTicks(u64 ticks) override { void AddTicks(u64 ticks) override {
if (parent.uses_wall_clock) {
return;
}
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off // rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should // if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4 // device a way so that timing is consistent across all cores without increasing the ticks 4
// times. // times.
u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES; u64 amortized_ticks =
(ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick. // Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1); amortized_ticks = std::max<u64>(amortized_ticks, 1);
parent.system.CoreTiming().AddTicks(amortized_ticks); parent.system.CoreTiming().AddTicks(amortized_ticks);
num_interpreted_instructions = 0; num_interpreted_instructions = 0;
} }
u64 GetTicksRemaining() override { u64 GetTicksRemaining() override {
return std::max(parent.system.CoreTiming().GetDowncount(), s64{0}); if (parent.uses_wall_clock) {
if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
return minimum_run_cycles;
}
return 0U;
}
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
} }
u64 GetCNTPCT() override { u64 GetCNTPCT() override {
return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks()); return parent.system.CoreTiming().GetClockTicks();
} }
ARM_Dynarmic_64& parent; ARM_Dynarmic_64& parent;
std::size_t num_interpreted_instructions = 0; std::size_t num_interpreted_instructions = 0;
u64 tpidrro_el0 = 0; u64 tpidrro_el0 = 0;
u64 tpidr_el0 = 0; u64 tpidr_el0 = 0;
static constexpr u64 minimum_run_cycles = 1000U;
}; };
std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& page_table, std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& page_table,
@ -168,14 +197,13 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
config.enable_fast_dispatch = false; config.enable_fast_dispatch = false;
} }
// Timing
config.wall_clock_cntpct = uses_wall_clock;
return std::make_shared<Dynarmic::A64::Jit>(config); return std::make_shared<Dynarmic::A64::Jit>(config);
} }
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_64, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
void ARM_Dynarmic_64::Run() { void ARM_Dynarmic_64::Run() {
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic_64);
jit->Run(); jit->Run();
} }
@ -183,11 +211,16 @@ void ARM_Dynarmic_64::Step() {
cb->InterpreterFallback(jit->GetPC(), 1); cb->InterpreterFallback(jit->GetPC(), 1);
} }
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor, ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handlers,
bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
std::size_t core_index) std::size_t core_index)
: ARM_Interface{system}, cb(std::make_unique<DynarmicCallbacks64>(*this)), : ARM_Interface{system, interrupt_handlers, uses_wall_clock},
inner_unicorn{system, ARM_Unicorn::Arch::AArch64}, core_index{core_index}, cb(std::make_unique<DynarmicCallbacks64>(*this)), inner_unicorn{system, interrupt_handlers,
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {} uses_wall_clock,
ARM_Unicorn::Arch::AArch64,
core_index},
core_index{core_index}, exclusive_monitor{
dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
ARM_Dynarmic_64::~ARM_Dynarmic_64() = default; ARM_Dynarmic_64::~ARM_Dynarmic_64() = default;
@ -239,6 +272,10 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) {
cb->tpidr_el0 = value; cb->tpidr_el0 = value;
} }
void ARM_Dynarmic_64::ChangeProcessorID(std::size_t new_core_id) {
jit->ChangeProcessorID(new_core_id);
}
void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) { void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
ctx.cpu_registers = jit->GetRegisters(); ctx.cpu_registers = jit->GetRegisters();
ctx.sp = jit->GetSP(); ctx.sp = jit->GetSP();
@ -266,6 +303,9 @@ void ARM_Dynarmic_64::PrepareReschedule() {
} }
void ARM_Dynarmic_64::ClearInstructionCache() { void ARM_Dynarmic_64::ClearInstructionCache() {
if (!jit) {
return;
}
jit->ClearCache(); jit->ClearCache();
} }
@ -285,44 +325,4 @@ void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table,
jit_cache.emplace(key, jit); jit_cache.emplace(key, jit);
} }
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
: monitor(core_count), memory{memory} {}
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
void DynarmicExclusiveMonitor::SetExclusive(std::size_t core_index, VAddr addr) {
// Size doesn't actually matter.
monitor.Mark(core_index, addr, 16);
}
void DynarmicExclusiveMonitor::ClearExclusive() {
monitor.Clear();
}
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
return monitor.DoExclusiveOperation(core_index, vaddr, 1, [&] { memory.Write8(vaddr, value); });
}
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
return monitor.DoExclusiveOperation(core_index, vaddr, 2,
[&] { memory.Write16(vaddr, value); });
}
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
return monitor.DoExclusiveOperation(core_index, vaddr, 4,
[&] { memory.Write32(vaddr, value); });
}
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
return monitor.DoExclusiveOperation(core_index, vaddr, 8,
[&] { memory.Write64(vaddr, value); });
}
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
return monitor.DoExclusiveOperation(core_index, vaddr, 16, [&] {
memory.Write64(vaddr + 0, value[0]);
memory.Write64(vaddr + 8, value[1]);
});
}
} // namespace Core } // namespace Core

@ -8,7 +8,6 @@
#include <unordered_map> #include <unordered_map>
#include <dynarmic/A64/a64.h> #include <dynarmic/A64/a64.h>
#include <dynarmic/A64/exclusive_monitor.h>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/hash.h" #include "common/hash.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
@ -22,12 +21,14 @@ class Memory;
namespace Core { namespace Core {
class DynarmicCallbacks64; class DynarmicCallbacks64;
class CPUInterruptHandler;
class DynarmicExclusiveMonitor; class DynarmicExclusiveMonitor;
class System; class System;
class ARM_Dynarmic_64 final : public ARM_Interface { class ARM_Dynarmic_64 final : public ARM_Interface {
public: public:
ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index); ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic_64() override; ~ARM_Dynarmic_64() override;
void SetPC(u64 pc) override; void SetPC(u64 pc) override;
@ -44,6 +45,7 @@ public:
void SetTlsAddress(VAddr address) override; void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override; void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override; u64 GetTPIDR_EL0() const override;
void ChangeProcessorID(std::size_t new_core_id) override;
void SaveContext(ThreadContext32& ctx) override {} void SaveContext(ThreadContext32& ctx) override {}
void SaveContext(ThreadContext64& ctx) override; void SaveContext(ThreadContext64& ctx) override;
@ -75,24 +77,4 @@ private:
DynarmicExclusiveMonitor& exclusive_monitor; DynarmicExclusiveMonitor& exclusive_monitor;
}; };
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
public:
explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
~DynarmicExclusiveMonitor() override;
void SetExclusive(std::size_t core_index, VAddr addr) override;
void ClearExclusive() override;
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override;
private:
friend class ARM_Dynarmic_64;
Dynarmic::A64::ExclusiveMonitor monitor;
Core::Memory::Memory& memory;
};
} // namespace Core } // namespace Core

@ -97,7 +97,7 @@ CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc
const auto callback = static_cast<u64 (*)(Dynarmic::A32::Jit*, void*, u32, u32)>( const auto callback = static_cast<u64 (*)(Dynarmic::A32::Jit*, void*, u32, u32)>(
[](Dynarmic::A32::Jit*, void* arg, u32, u32) -> u64 { [](Dynarmic::A32::Jit*, void* arg, u32, u32) -> u64 {
ARM_Dynarmic_32& parent = *(ARM_Dynarmic_32*)arg; ARM_Dynarmic_32& parent = *(ARM_Dynarmic_32*)arg;
return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks()); return parent.system.CoreTiming().GetClockTicks();
}); });
return Dynarmic::A32::Coprocessor::Callback{callback, (void*)&parent}; return Dynarmic::A32::Coprocessor::Callback{callback, (void*)&parent};
} }

@ -0,0 +1,76 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cinttypes>
#include <memory>
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/memory.h"
namespace Core {
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
: monitor(core_count), memory{memory} {}
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
}
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
}
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
}
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
}
u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) {
return monitor.ReadAndMark<u128>(core_index, addr, [&]() -> u128 {
u128 result;
result[0] = memory.Read64(addr);
result[1] = memory.Read64(addr + 8);
return result;
});
}
void DynarmicExclusiveMonitor::ClearExclusive() {
monitor.Clear();
}
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
return memory.WriteExclusive8(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
return memory.WriteExclusive16(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
return memory.WriteExclusive32(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
return memory.WriteExclusive64(vaddr, value, expected);
});
}
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [&](u128 expected) -> bool {
return memory.WriteExclusive128(vaddr, value, expected);
});
}
} // namespace Core

@ -0,0 +1,48 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <unordered_map>
#include <dynarmic/exclusive_monitor.h>
#include "common/common_types.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#include "core/arm/exclusive_monitor.h"
namespace Core::Memory {
class Memory;
}
namespace Core {
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
public:
explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
~DynarmicExclusiveMonitor() override;
u8 ExclusiveRead8(std::size_t core_index, VAddr addr) override;
u16 ExclusiveRead16(std::size_t core_index, VAddr addr) override;
u32 ExclusiveRead32(std::size_t core_index, VAddr addr) override;
u64 ExclusiveRead64(std::size_t core_index, VAddr addr) override;
u128 ExclusiveRead128(std::size_t core_index, VAddr addr) override;
void ClearExclusive() override;
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override;
private:
friend class ARM_Dynarmic_32;
friend class ARM_Dynarmic_64;
Dynarmic::ExclusiveMonitor monitor;
Core::Memory::Memory& memory;
};
} // namespace Core

@ -3,7 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic_64.h" #include "core/arm/dynarmic/arm_exclusive_monitor.h"
#endif #endif
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/memory.h" #include "core/memory.h"

@ -18,7 +18,11 @@ class ExclusiveMonitor {
public: public:
virtual ~ExclusiveMonitor(); virtual ~ExclusiveMonitor();
virtual void SetExclusive(std::size_t core_index, VAddr addr) = 0; virtual u8 ExclusiveRead8(std::size_t core_index, VAddr addr) = 0;
virtual u16 ExclusiveRead16(std::size_t core_index, VAddr addr) = 0;
virtual u32 ExclusiveRead32(std::size_t core_index, VAddr addr) = 0;
virtual u64 ExclusiveRead64(std::size_t core_index, VAddr addr) = 0;
virtual u128 ExclusiveRead128(std::size_t core_index, VAddr addr) = 0;
virtual void ClearExclusive() = 0; virtual void ClearExclusive() = 0;
virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0; virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0;

@ -6,6 +6,7 @@
#include <unicorn/arm64.h> #include <unicorn/arm64.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/unicorn/arm_unicorn.h" #include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
@ -62,7 +63,9 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si
return false; return false;
} }
ARM_Unicorn::ARM_Unicorn(System& system, Arch architecture) : ARM_Interface{system} { ARM_Unicorn::ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
Arch architecture, std::size_t core_index)
: ARM_Interface{system, interrupt_handlers, uses_wall_clock}, core_index{core_index} {
const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64; const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64;
CHECKED(uc_open(arch, UC_MODE_ARM, &uc)); CHECKED(uc_open(arch, UC_MODE_ARM, &uc));
@ -156,12 +159,20 @@ void ARM_Unicorn::SetTPIDR_EL0(u64 value) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value)); CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value));
} }
void ARM_Unicorn::ChangeProcessorID(std::size_t new_core_id) {
core_index = new_core_id;
}
void ARM_Unicorn::Run() { void ARM_Unicorn::Run() {
if (GDBStub::IsServerEnabled()) { if (GDBStub::IsServerEnabled()) {
ExecuteInstructions(std::max(4000000U, 0U)); ExecuteInstructions(std::max(4000000U, 0U));
} else { } else {
ExecuteInstructions( while (true) {
std::max(std::size_t(system.CoreTiming().GetDowncount()), std::size_t{0})); if (interrupt_handlers[core_index].IsInterrupted()) {
return;
}
ExecuteInstructions(10);
}
} }
} }
@ -183,8 +194,6 @@ void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) {
UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data())); UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data()));
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions)); CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size())); CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size()));
system.CoreTiming().AddTicks(num_instructions);
if (GDBStub::IsServerEnabled()) { if (GDBStub::IsServerEnabled()) {
if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) { if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);

@ -20,7 +20,8 @@ public:
AArch64, // 64-bit ARM AArch64, // 64-bit ARM
}; };
explicit ARM_Unicorn(System& system, Arch architecture); explicit ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
Arch architecture, std::size_t core_index);
~ARM_Unicorn() override; ~ARM_Unicorn() override;
void SetPC(u64 pc) override; void SetPC(u64 pc) override;
@ -35,6 +36,7 @@ public:
void SetTlsAddress(VAddr address) override; void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override; void SetTPIDR_EL0(u64 value) override;
u64 GetTPIDR_EL0() const override; u64 GetTPIDR_EL0() const override;
void ChangeProcessorID(std::size_t new_core_id) override;
void PrepareReschedule() override; void PrepareReschedule() override;
void ClearExclusiveState() override; void ClearExclusiveState() override;
void ExecuteInstructions(std::size_t num_instructions); void ExecuteInstructions(std::size_t num_instructions);
@ -55,6 +57,7 @@ private:
uc_engine* uc{}; uc_engine* uc{};
GDBStub::BreakpointAddress last_bkpt{}; GDBStub::BreakpointAddress last_bkpt{};
bool last_bkpt_hit = false; bool last_bkpt_hit = false;
std::size_t core_index;
}; };
} // namespace Core } // namespace Core

@ -8,10 +8,10 @@
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/cpu_manager.h" #include "core/cpu_manager.h"
#include "core/device_memory.h" #include "core/device_memory.h"
@ -51,6 +51,11 @@
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU0, "ARM JIT", "Dynarmic CPU 0", MP_RGB(255, 64, 64));
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU1, "ARM JIT", "Dynarmic CPU 1", MP_RGB(255, 64, 64));
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU2, "ARM JIT", "Dynarmic CPU 2", MP_RGB(255, 64, 64));
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU3, "ARM JIT", "Dynarmic CPU 3", MP_RGB(255, 64, 64));
namespace Core { namespace Core {
namespace { namespace {
@ -117,23 +122,22 @@ struct System::Impl {
: kernel{system}, fs_controller{system}, memory{system}, : kernel{system}, fs_controller{system}, memory{system},
cpu_manager{system}, reporter{system}, applet_manager{system} {} cpu_manager{system}, reporter{system}, applet_manager{system} {}
CoreManager& CurrentCoreManager() { ResultStatus Run() {
return cpu_manager.GetCurrentCoreManager();
}
Kernel::PhysicalCore& CurrentPhysicalCore() {
const auto index = cpu_manager.GetActiveCoreIndex();
return kernel.PhysicalCore(index);
}
Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) {
return kernel.PhysicalCore(index);
}
ResultStatus RunLoop(bool tight_loop) {
status = ResultStatus::Success; status = ResultStatus::Success;
cpu_manager.RunLoop(tight_loop); kernel.Suspend(false);
core_timing.SyncPause(false);
cpu_manager.Pause(false);
return status;
}
ResultStatus Pause() {
status = ResultStatus::Success;
core_timing.SyncPause(true);
kernel.Suspend(true);
cpu_manager.Pause(true);
return status; return status;
} }
@ -143,7 +147,15 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>(system); device_memory = std::make_unique<Core::DeviceMemory>(system);
core_timing.Initialize(); is_multicore = Settings::values.use_multi_core;
is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation;
kernel.SetMulticore(is_multicore);
cpu_manager.SetMulticore(is_multicore);
cpu_manager.SetAsyncGpu(is_async_gpu);
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
kernel.Initialize(); kernel.Initialize();
cpu_manager.Initialize(); cpu_manager.Initialize();
@ -180,6 +192,11 @@ struct System::Impl {
is_powered_on = true; is_powered_on = true;
exit_lock = false; exit_lock = false;
microprofile_dynarmic[0] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU0);
microprofile_dynarmic[1] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU1);
microprofile_dynarmic[2] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU2);
microprofile_dynarmic[3] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU3);
LOG_DEBUG(Core, "Initialized OK"); LOG_DEBUG(Core, "Initialized OK");
return ResultStatus::Success; return ResultStatus::Success;
@ -277,8 +294,6 @@ struct System::Impl {
service_manager.reset(); service_manager.reset();
cheat_engine.reset(); cheat_engine.reset();
telemetry_session.reset(); telemetry_session.reset();
perf_stats.reset();
gpu_core.reset();
device_memory.reset(); device_memory.reset();
// Close all CPU/threading state // Close all CPU/threading state
@ -290,6 +305,8 @@ struct System::Impl {
// Close app loader // Close app loader
app_loader.reset(); app_loader.reset();
gpu_core.reset();
perf_stats.reset();
// Clear all applets // Clear all applets
applet_manager.ClearAll(); applet_manager.ClearAll();
@ -382,25 +399,35 @@ struct System::Impl {
std::unique_ptr<Core::PerfStats> perf_stats; std::unique_ptr<Core::PerfStats> perf_stats;
Core::FrameLimiter frame_limiter; Core::FrameLimiter frame_limiter;
bool is_multicore{};
bool is_async_gpu{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{};
}; };
System::System() : impl{std::make_unique<Impl>(*this)} {} System::System() : impl{std::make_unique<Impl>(*this)} {}
System::~System() = default; System::~System() = default;
CoreManager& System::CurrentCoreManager() { CpuManager& System::GetCpuManager() {
return impl->CurrentCoreManager(); return impl->cpu_manager;
} }
const CoreManager& System::CurrentCoreManager() const { const CpuManager& System::GetCpuManager() const {
return impl->CurrentCoreManager(); return impl->cpu_manager;
} }
System::ResultStatus System::RunLoop(bool tight_loop) { System::ResultStatus System::Run() {
return impl->RunLoop(tight_loop); return impl->Run();
}
System::ResultStatus System::Pause() {
return impl->Pause();
} }
System::ResultStatus System::SingleStep() { System::ResultStatus System::SingleStep() {
return RunLoop(false); return ResultStatus::Success;
} }
void System::InvalidateCpuInstructionCaches() { void System::InvalidateCpuInstructionCaches() {
@ -416,7 +443,7 @@ bool System::IsPoweredOn() const {
} }
void System::PrepareReschedule() { void System::PrepareReschedule() {
impl->CurrentPhysicalCore().Stop(); // Deprecated, does nothing, kept for backward compatibility.
} }
void System::PrepareReschedule(const u32 core_index) { void System::PrepareReschedule(const u32 core_index) {
@ -436,31 +463,41 @@ const TelemetrySession& System::TelemetrySession() const {
} }
ARM_Interface& System::CurrentArmInterface() { ARM_Interface& System::CurrentArmInterface() {
return impl->CurrentPhysicalCore().ArmInterface(); return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
} }
const ARM_Interface& System::CurrentArmInterface() const { const ARM_Interface& System::CurrentArmInterface() const {
return impl->CurrentPhysicalCore().ArmInterface(); return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
} }
std::size_t System::CurrentCoreIndex() const { std::size_t System::CurrentCoreIndex() const {
return impl->cpu_manager.GetActiveCoreIndex(); std::size_t core = impl->kernel.GetCurrentHostThreadID();
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
return core;
} }
Kernel::Scheduler& System::CurrentScheduler() { Kernel::Scheduler& System::CurrentScheduler() {
return impl->CurrentPhysicalCore().Scheduler(); return impl->kernel.CurrentScheduler();
} }
const Kernel::Scheduler& System::CurrentScheduler() const { const Kernel::Scheduler& System::CurrentScheduler() const {
return impl->CurrentPhysicalCore().Scheduler(); return impl->kernel.CurrentScheduler();
}
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
return impl->kernel.CurrentPhysicalCore();
}
const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
return impl->kernel.CurrentPhysicalCore();
} }
Kernel::Scheduler& System::Scheduler(std::size_t core_index) { Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
return impl->GetPhysicalCore(core_index).Scheduler(); return impl->kernel.Scheduler(core_index);
} }
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const { const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
return impl->GetPhysicalCore(core_index).Scheduler(); return impl->kernel.Scheduler(core_index);
} }
/// Gets the global scheduler /// Gets the global scheduler
@ -490,20 +527,15 @@ const Kernel::Process* System::CurrentProcess() const {
} }
ARM_Interface& System::ArmInterface(std::size_t core_index) { ARM_Interface& System::ArmInterface(std::size_t core_index) {
return impl->GetPhysicalCore(core_index).ArmInterface(); auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
ASSERT(thread && !thread->IsHLEThread());
return thread->ArmInterface();
} }
const ARM_Interface& System::ArmInterface(std::size_t core_index) const { const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
return impl->GetPhysicalCore(core_index).ArmInterface(); auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
} ASSERT(thread && !thread->IsHLEThread());
return thread->ArmInterface();
CoreManager& System::GetCoreManager(std::size_t core_index) {
return impl->cpu_manager.GetCoreManager(core_index);
}
const CoreManager& System::GetCoreManager(std::size_t core_index) const {
ASSERT(core_index < NUM_CPU_CORES);
return impl->cpu_manager.GetCoreManager(core_index);
} }
ExclusiveMonitor& System::Monitor() { ExclusiveMonitor& System::Monitor() {
@ -722,4 +754,18 @@ void System::RegisterHostThread() {
impl->kernel.RegisterHostThread(); impl->kernel.RegisterHostThread();
} }
void System::EnterDynarmicProfile() {
std::size_t core = impl->kernel.GetCurrentHostThreadID();
impl->dynarmic_ticks[core] = MicroProfileEnter(impl->microprofile_dynarmic[core]);
}
void System::ExitDynarmicProfile() {
std::size_t core = impl->kernel.GetCurrentHostThreadID();
MicroProfileLeave(impl->microprofile_dynarmic[core], impl->dynarmic_ticks[core]);
}
bool System::IsMulticore() const {
return impl->is_multicore;
}
} // namespace Core } // namespace Core

@ -27,6 +27,7 @@ class VfsFilesystem;
namespace Kernel { namespace Kernel {
class GlobalScheduler; class GlobalScheduler;
class KernelCore; class KernelCore;
class PhysicalCore;
class Process; class Process;
class Scheduler; class Scheduler;
} // namespace Kernel } // namespace Kernel
@ -90,7 +91,7 @@ class InterruptManager;
namespace Core { namespace Core {
class ARM_Interface; class ARM_Interface;
class CoreManager; class CpuManager;
class DeviceMemory; class DeviceMemory;
class ExclusiveMonitor; class ExclusiveMonitor;
class FrameLimiter; class FrameLimiter;
@ -136,16 +137,16 @@ public:
}; };
/** /**
* Run the core CPU loop * Run the OS and Application
* This function runs the core for the specified number of CPU instructions before trying to * This function will start emulation and run the relevant devices
* update hardware. This is much faster than SingleStep (and should be equivalent), as the CPU
* is not required to do a full dispatch with each instruction. NOTE: the number of instructions
* requested is not guaranteed to run, as this will be interrupted preemptively if a hardware
* update is requested (e.g. on a thread switch).
* @param tight_loop If false, the CPU single-steps.
* @return Result status, indicating whether or not the operation succeeded.
*/ */
ResultStatus RunLoop(bool tight_loop = true); ResultStatus Run();
/**
* Pause the OS and Application
* This function will pause emulation and stop the relevant devices
*/
ResultStatus Pause();
/** /**
* Step the CPU one instruction * Step the CPU one instruction
@ -209,17 +210,21 @@ public:
/// Gets the scheduler for the CPU core that is currently running /// Gets the scheduler for the CPU core that is currently running
const Kernel::Scheduler& CurrentScheduler() const; const Kernel::Scheduler& CurrentScheduler() const;
/// Gets the physical core for the CPU core that is currently running
Kernel::PhysicalCore& CurrentPhysicalCore();
/// Gets the physical core for the CPU core that is currently running
const Kernel::PhysicalCore& CurrentPhysicalCore() const;
/// Gets a reference to an ARM interface for the CPU core with the specified index /// Gets a reference to an ARM interface for the CPU core with the specified index
ARM_Interface& ArmInterface(std::size_t core_index); ARM_Interface& ArmInterface(std::size_t core_index);
/// Gets a const reference to an ARM interface from the CPU core with the specified index /// Gets a const reference to an ARM interface from the CPU core with the specified index
const ARM_Interface& ArmInterface(std::size_t core_index) const; const ARM_Interface& ArmInterface(std::size_t core_index) const;
/// Gets a CPU interface to the CPU core with the specified index CpuManager& GetCpuManager();
CoreManager& GetCoreManager(std::size_t core_index);
/// Gets a CPU interface to the CPU core with the specified index const CpuManager& GetCpuManager() const;
const CoreManager& GetCoreManager(std::size_t core_index) const;
/// Gets a reference to the exclusive monitor /// Gets a reference to the exclusive monitor
ExclusiveMonitor& Monitor(); ExclusiveMonitor& Monitor();
@ -370,15 +375,18 @@ public:
/// Register a host thread as an auxiliary thread. /// Register a host thread as an auxiliary thread.
void RegisterHostThread(); void RegisterHostThread();
/// Enter Dynarmic Microprofile
void EnterDynarmicProfile();
/// Exit Dynarmic Microprofile
void ExitDynarmicProfile();
/// Tells if system is running on multicore.
bool IsMulticore() const;
private: private:
System(); System();
/// Returns the currently running CPU core
CoreManager& CurrentCoreManager();
/// Returns the currently running CPU core
const CoreManager& CurrentCoreManager() const;
/** /**
* Initialize the emulated system. * Initialize the emulated system.
* @param emu_window Reference to the host-system window used for video output and keyboard * @param emu_window Reference to the host-system window used for video output and keyboard

@ -1,67 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <condition_variable>
#include <mutex>
#include "common/logging/log.h"
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/settings.h"
namespace Core {
CoreManager::CoreManager(System& system, std::size_t core_index)
: global_scheduler{system.GlobalScheduler()}, physical_core{system.Kernel().PhysicalCore(
core_index)},
core_timing{system.CoreTiming()}, core_index{core_index} {}
CoreManager::~CoreManager() = default;
void CoreManager::RunLoop(bool tight_loop) {
Reschedule();
// If we don't have a currently active thread then don't execute instructions,
// instead advance to the next event and try to yield to the next thread
if (Kernel::GetCurrentThread() == nullptr) {
LOG_TRACE(Core, "Core-{} idling", core_index);
core_timing.Idle();
} else {
if (tight_loop) {
physical_core.Run();
} else {
physical_core.Step();
}
}
core_timing.Advance();
Reschedule();
}
void CoreManager::SingleStep() {
return RunLoop(false);
}
void CoreManager::PrepareReschedule() {
physical_core.Stop();
}
void CoreManager::Reschedule() {
// Lock the global kernel mutex when we manipulate the HLE state
std::lock_guard lock(HLE::g_hle_lock);
global_scheduler.SelectThread(core_index);
physical_core.Scheduler().TryDoContextSwitch();
}
} // namespace Core

@ -1,63 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <cstddef>
#include <memory>
#include "common/common_types.h"
namespace Kernel {
class GlobalScheduler;
class PhysicalCore;
} // namespace Kernel
namespace Core {
class System;
}
namespace Core::Timing {
class CoreTiming;
}
namespace Core::Memory {
class Memory;
}
namespace Core {
constexpr unsigned NUM_CPU_CORES{4};
class CoreManager {
public:
CoreManager(System& system, std::size_t core_index);
~CoreManager();
void RunLoop(bool tight_loop = true);
void SingleStep();
void PrepareReschedule();
bool IsMainCore() const {
return core_index == 0;
}
std::size_t CoreIndex() const {
return core_index;
}
private:
void Reschedule();
Kernel::GlobalScheduler& global_scheduler;
Kernel::PhysicalCore& physical_core;
Timing::CoreTiming& core_timing;
std::atomic<bool> reschedule_pending = false;
std::size_t core_index;
};
} // namespace Core

@ -1,29 +1,27 @@
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project // Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2+ // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "core/core_timing.h"
#include <algorithm> #include <algorithm>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include "common/assert.h" #include "common/assert.h"
#include "common/thread.h" #include "common/microprofile.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/hardware_properties.h"
namespace Core::Timing { namespace Core::Timing {
constexpr int MAX_SLICE_LENGTH = 10000; constexpr u64 MAX_SLICE_LENGTH = 4000;
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) { std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
return std::make_shared<EventType>(std::move(callback), std::move(name)); return std::make_shared<EventType>(std::move(callback), std::move(name));
} }
struct CoreTiming::Event { struct CoreTiming::Event {
s64 time; u64 time;
u64 fifo_order; u64 fifo_order;
u64 userdata; u64 userdata;
std::weak_ptr<EventType> type; std::weak_ptr<EventType> type;
@ -39,51 +37,90 @@ struct CoreTiming::Event {
} }
}; };
CoreTiming::CoreTiming() = default; CoreTiming::CoreTiming() {
clock =
Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ);
}
CoreTiming::~CoreTiming() = default; CoreTiming::~CoreTiming() = default;
void CoreTiming::Initialize() { void CoreTiming::ThreadEntry(CoreTiming& instance) {
downcounts.fill(MAX_SLICE_LENGTH); constexpr char name[] = "yuzu:HostTiming";
time_slice.fill(MAX_SLICE_LENGTH); MicroProfileOnThreadCreate(name);
slice_length = MAX_SLICE_LENGTH; Common::SetCurrentThreadName(name);
global_timer = 0; Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
idled_cycles = 0; instance.on_thread_init();
current_context = 0; instance.ThreadLoop();
}
// The time between CoreTiming being initialized and the first call to Advance() is considered
// the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
// executing the first cycle of each slice to prepare the slice length and downcount for
// that slice.
is_global_timer_sane = true;
void CoreTiming::Initialize(std::function<void(void)>&& on_thread_init_) {
on_thread_init = std::move(on_thread_init_);
event_fifo_id = 0; event_fifo_id = 0;
shutting_down = false;
ticks = 0;
const auto empty_timed_callback = [](u64, s64) {}; const auto empty_timed_callback = [](u64, s64) {};
ev_lost = CreateEvent("_lost_event", empty_timed_callback); ev_lost = CreateEvent("_lost_event", empty_timed_callback);
if (is_multicore) {
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
}
} }
void CoreTiming::Shutdown() { void CoreTiming::Shutdown() {
paused = true;
shutting_down = true;
pause_event.Set();
event.Set();
if (timer_thread) {
timer_thread->join();
}
ClearPendingEvents(); ClearPendingEvents();
timer_thread.reset();
has_started = false;
} }
void CoreTiming::ScheduleEvent(s64 cycles_into_future, const std::shared_ptr<EventType>& event_type, void CoreTiming::Pause(bool is_paused) {
u64 userdata) { paused = is_paused;
std::lock_guard guard{inner_mutex}; pause_event.Set();
const s64 timeout = GetTicks() + cycles_into_future; }
// If this event needs to be scheduled before the next advance(), force one early void CoreTiming::SyncPause(bool is_paused) {
if (!is_global_timer_sane) { if (is_paused == paused && paused_set == paused) {
ForceExceptionCheck(cycles_into_future); return;
} }
Pause(is_paused);
if (timer_thread) {
if (!is_paused) {
pause_event.Set();
}
event.Set();
while (paused_set != is_paused)
;
}
}
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type}); bool CoreTiming::IsRunning() const {
return !paused_set;
}
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); bool CoreTiming::HasPendingEvents() const {
return !(wait_set && event_queue.empty());
}
void CoreTiming::ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type,
u64 userdata) {
{
std::scoped_lock scope{basic_lock};
const u64 timeout = static_cast<u64>(GetGlobalTimeNs().count() + ns_into_future);
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
event.Set();
} }
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) { void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) {
std::lock_guard guard{inner_mutex}; std::scoped_lock scope{basic_lock};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type.lock().get() == event_type.get() && e.userdata == userdata; return e.type.lock().get() == event_type.get() && e.userdata == userdata;
}); });
@ -95,21 +132,39 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u
} }
} }
u64 CoreTiming::GetTicks() const { void CoreTiming::AddTicks(u64 ticks) {
u64 ticks = static_cast<u64>(global_timer); this->ticks += ticks;
if (!is_global_timer_sane) { downcount -= ticks;
ticks += accumulated_ticks; }
void CoreTiming::Idle() {
if (!event_queue.empty()) {
const u64 next_event_time = event_queue.front().time;
const u64 next_ticks = nsToCycles(std::chrono::nanoseconds(next_event_time)) + 10U;
if (next_ticks > ticks) {
ticks = next_ticks;
}
return;
}
ticks += 1000U;
}
void CoreTiming::ResetTicks() {
downcount = MAX_SLICE_LENGTH;
}
u64 CoreTiming::GetCPUTicks() const {
if (is_multicore) {
return clock->GetCPUCycles();
} }
return ticks; return ticks;
} }
u64 CoreTiming::GetIdleTicks() const { u64 CoreTiming::GetClockTicks() const {
return static_cast<u64>(idled_cycles); if (is_multicore) {
} return clock->GetClockCycles();
}
void CoreTiming::AddTicks(u64 ticks) { return CpuCyclesToClockCycles(ticks);
accumulated_ticks += ticks;
downcounts[current_context] -= static_cast<s64>(ticks);
} }
void CoreTiming::ClearPendingEvents() { void CoreTiming::ClearPendingEvents() {
@ -117,7 +172,7 @@ void CoreTiming::ClearPendingEvents() {
} }
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
std::lock_guard guard{inner_mutex}; basic_lock.lock();
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type.lock().get() == event_type.get(); return e.type.lock().get() == event_type.get();
@ -128,99 +183,72 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
event_queue.erase(itr, event_queue.end()); event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
} }
basic_lock.unlock();
} }
void CoreTiming::ForceExceptionCheck(s64 cycles) { std::optional<s64> CoreTiming::Advance() {
cycles = std::max<s64>(0, cycles); std::scoped_lock advance_scope{advance_lock};
if (downcounts[current_context] <= cycles) { std::scoped_lock basic_scope{basic_lock};
return; global_timer = GetGlobalTimeNs().count();
}
// downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int
// here. Account for cycles already executed by adjusting the g.slice_length
downcounts[current_context] = static_cast<int>(cycles);
}
std::optional<u64> CoreTiming::NextAvailableCore(const s64 needed_ticks) const {
const u64 original_context = current_context;
u64 next_context = (original_context + 1) % num_cpu_cores;
while (next_context != original_context) {
if (time_slice[next_context] >= needed_ticks) {
return {next_context};
} else if (time_slice[next_context] >= 0) {
return std::nullopt;
}
next_context = (next_context + 1) % num_cpu_cores;
}
return std::nullopt;
}
void CoreTiming::Advance() {
std::unique_lock<std::mutex> guard(inner_mutex);
const u64 cycles_executed = accumulated_ticks;
time_slice[current_context] = std::max<s64>(0, time_slice[current_context] - accumulated_ticks);
global_timer += cycles_executed;
is_global_timer_sane = true;
while (!event_queue.empty() && event_queue.front().time <= global_timer) { while (!event_queue.empty() && event_queue.front().time <= global_timer) {
Event evt = std::move(event_queue.front()); Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
event_queue.pop_back(); event_queue.pop_back();
inner_mutex.unlock(); basic_lock.unlock();
if (auto event_type{evt.type.lock()}) { if (auto event_type{evt.type.lock()}) {
event_type->callback(evt.userdata, global_timer - evt.time); event_type->callback(evt.userdata, global_timer - evt.time);
} }
inner_mutex.lock(); basic_lock.lock();
global_timer = GetGlobalTimeNs().count();
} }
is_global_timer_sane = false;
// Still events left (scheduled in the future)
if (!event_queue.empty()) { if (!event_queue.empty()) {
const s64 needed_ticks = const s64 next_time = event_queue.front().time - global_timer;
std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH); return next_time;
const auto next_core = NextAvailableCore(needed_ticks); } else {
if (next_core) { return std::nullopt;
downcounts[*next_core] = needed_ticks; }
}
void CoreTiming::ThreadLoop() {
has_started = true;
while (!shutting_down) {
while (!paused) {
paused_set = false;
const auto next_time = Advance();
if (next_time) {
if (*next_time > 0) {
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
event.WaitFor(next_time_ns);
}
} else {
wait_set = true;
event.Wait();
}
wait_set = false;
} }
paused_set = true;
clock->Pause(true);
pause_event.Wait();
clock->Pause(false);
} }
accumulated_ticks = 0;
downcounts[current_context] = time_slice[current_context];
} }
void CoreTiming::ResetRun() { std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
downcounts.fill(MAX_SLICE_LENGTH); if (is_multicore) {
time_slice.fill(MAX_SLICE_LENGTH); return clock->GetTimeNS();
current_context = 0;
// Still events left (scheduled in the future)
if (!event_queue.empty()) {
const s64 needed_ticks =
std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH);
downcounts[current_context] = needed_ticks;
} }
return CyclesToNs(ticks);
is_global_timer_sane = false;
accumulated_ticks = 0;
}
void CoreTiming::Idle() {
accumulated_ticks += downcounts[current_context];
idled_cycles += downcounts[current_context];
downcounts[current_context] = 0;
} }
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
return std::chrono::microseconds{GetTicks() * 1000000 / Hardware::BASE_CLOCK_RATE}; if (is_multicore) {
} return clock->GetTimeUS();
}
s64 CoreTiming::GetDowncount() const { return CyclesToUs(ticks);
return downcounts[current_context];
} }
} // namespace Core::Timing } // namespace Core::Timing

@ -1,19 +1,25 @@
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project // Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2+ // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <atomic>
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <string> #include <string>
#include <thread>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/spin_lock.h"
#include "common/thread.h"
#include "common/threadsafe_queue.h" #include "common/threadsafe_queue.h"
#include "common/wall_clock.h"
#include "core/hardware_properties.h"
namespace Core::Timing { namespace Core::Timing {
@ -56,16 +62,40 @@ public:
/// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
/// required to end slice - 1 and start slice 0 before the first cycle of code is executed. /// required to end slice - 1 and start slice 0 before the first cycle of code is executed.
void Initialize(); void Initialize(std::function<void(void)>&& on_thread_init_);
/// Tears down all timing related functionality. /// Tears down all timing related functionality.
void Shutdown(); void Shutdown();
/// After the first Advance, the slice lengths and the downcount will be reduced whenever an /// Sets if emulation is multicore or single core, must be set before Initialize
/// event is scheduled earlier than the current values. void SetMulticore(bool is_multicore) {
/// this->is_multicore = is_multicore;
/// Scheduling from a callback will not update the downcount until the Advance() completes. }
void ScheduleEvent(s64 cycles_into_future, const std::shared_ptr<EventType>& event_type,
/// Check if it's using host timing.
bool IsHostTiming() const {
return is_multicore;
}
/// Pauses/Unpauses the execution of the timer thread.
void Pause(bool is_paused);
/// Pauses/Unpauses the execution of the timer thread and waits until paused.
void SyncPause(bool is_paused);
/// Checks if core timing is running.
bool IsRunning() const;
/// Checks if the timer thread has started.
bool HasStarted() const {
return has_started;
}
/// Checks if there are any pending time events.
bool HasPendingEvents() const;
/// Schedules an event in core timing
void ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type,
u64 userdata = 0); u64 userdata = 0);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata); void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata);
@ -73,41 +103,30 @@ public:
/// We only permit one event of each type in the queue at a time. /// We only permit one event of each type in the queue at a time.
void RemoveEvent(const std::shared_ptr<EventType>& event_type); void RemoveEvent(const std::shared_ptr<EventType>& event_type);
void ForceExceptionCheck(s64 cycles);
/// This should only be called from the emu thread, if you are calling it any other thread,
/// you are doing something evil
u64 GetTicks() const;
u64 GetIdleTicks() const;
void AddTicks(u64 ticks); void AddTicks(u64 ticks);
/// Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends void ResetTicks();
/// the previous timing slice and begins the next one, you must Advance from the previous
/// slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an
/// Advance() is required to initialize the slice length before the first cycle of emulated
/// instructions is executed.
void Advance();
/// Pretend that the main CPU has executed enough cycles to reach the next event.
void Idle(); void Idle();
s64 GetDowncount() const {
return downcount;
}
/// Returns current time in emulated CPU cycles
u64 GetCPUTicks() const;
/// Returns current time in emulated in Clock cycles
u64 GetClockTicks() const;
/// Returns current time in microseconds.
std::chrono::microseconds GetGlobalTimeUs() const; std::chrono::microseconds GetGlobalTimeUs() const;
void ResetRun(); /// Returns current time in nanoseconds.
std::chrono::nanoseconds GetGlobalTimeNs() const;
s64 GetDowncount() const; /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
std::optional<s64> Advance();
void SwitchContext(u64 new_context) {
current_context = new_context;
}
bool CanCurrentContextRun() const {
return time_slice[current_context] > 0;
}
std::optional<u64> NextAvailableCore(const s64 needed_ticks) const;
private: private:
struct Event; struct Event;
@ -115,21 +134,14 @@ private:
/// Clear all pending events. This should ONLY be done on exit. /// Clear all pending events. This should ONLY be done on exit.
void ClearPendingEvents(); void ClearPendingEvents();
static constexpr u64 num_cpu_cores = 4; static void ThreadEntry(CoreTiming& instance);
void ThreadLoop();
s64 global_timer = 0; std::unique_ptr<Common::WallClock> clock;
s64 idled_cycles = 0;
s64 slice_length = 0;
u64 accumulated_ticks = 0;
std::array<s64, num_cpu_cores> downcounts{};
// Slice of time assigned to each core per run.
std::array<s64, num_cpu_cores> time_slice{};
u64 current_context = 0;
// Are we in a function that has been called from Advance() u64 global_timer = 0;
// If events are scheduled from a function that gets called from Advance(),
// don't change slice_length and downcount. std::chrono::nanoseconds start_point;
bool is_global_timer_sane = false;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap. // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and // We don't use std::priority_queue because we need to be able to serialize, unserialize and
@ -139,8 +151,23 @@ private:
u64 event_fifo_id = 0; u64 event_fifo_id = 0;
std::shared_ptr<EventType> ev_lost; std::shared_ptr<EventType> ev_lost;
Common::Event event{};
Common::Event pause_event{};
Common::SpinLock basic_lock{};
Common::SpinLock advance_lock{};
std::unique_ptr<std::thread> timer_thread;
std::atomic<bool> paused{};
std::atomic<bool> paused_set{};
std::atomic<bool> wait_set{};
std::atomic<bool> shutting_down{};
std::atomic<bool> has_started{};
std::function<void(void)> on_thread_init{};
std::mutex inner_mutex; bool is_multicore{};
/// Cycle timing
u64 ticks{};
s64 downcount{};
}; };
/// Creates a core timing event with the given name and callback. /// Creates a core timing event with the given name and callback.

@ -38,15 +38,23 @@ s64 usToCycles(std::chrono::microseconds us) {
} }
s64 nsToCycles(std::chrono::nanoseconds ns) { s64 nsToCycles(std::chrono::nanoseconds ns) {
if (static_cast<u64>(ns.count() / 1000000000) > MAX_VALUE_TO_MULTIPLY) { const u128 temporal = Common::Multiply64Into128(ns.count(), Hardware::BASE_CLOCK_RATE);
LOG_ERROR(Core_Timing, "Integer overflow, use max value"); return Common::Divide128On32(temporal, static_cast<u32>(1000000000)).first;
return std::numeric_limits<s64>::max(); }
}
if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) { u64 msToClockCycles(std::chrono::milliseconds ns) {
LOG_DEBUG(Core_Timing, "Time very big, do rounding"); const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
return Hardware::BASE_CLOCK_RATE * (ns.count() / 1000000000); return Common::Divide128On32(temp, 1000).first;
} }
return (Hardware::BASE_CLOCK_RATE * ns.count()) / 1000000000;
u64 usToClockCycles(std::chrono::microseconds ns) {
const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
return Common::Divide128On32(temp, 1000000).first;
}
u64 nsToClockCycles(std::chrono::nanoseconds ns) {
const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
return Common::Divide128On32(temp, 1000000000).first;
} }
u64 CpuCyclesToClockCycles(u64 ticks) { u64 CpuCyclesToClockCycles(u64 ticks) {
@ -54,4 +62,22 @@ u64 CpuCyclesToClockCycles(u64 ticks) {
return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first; return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
} }
std::chrono::milliseconds CyclesToMs(s64 cycles) {
const u128 temporal = Common::Multiply64Into128(cycles, 1000);
u64 ms = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
return std::chrono::milliseconds(ms);
}
std::chrono::nanoseconds CyclesToNs(s64 cycles) {
const u128 temporal = Common::Multiply64Into128(cycles, 1000000000);
u64 ns = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
return std::chrono::nanoseconds(ns);
}
std::chrono::microseconds CyclesToUs(s64 cycles) {
const u128 temporal = Common::Multiply64Into128(cycles, 1000000);
u64 us = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
return std::chrono::microseconds(us);
}
} // namespace Core::Timing } // namespace Core::Timing

@ -13,18 +13,12 @@ namespace Core::Timing {
s64 msToCycles(std::chrono::milliseconds ms); s64 msToCycles(std::chrono::milliseconds ms);
s64 usToCycles(std::chrono::microseconds us); s64 usToCycles(std::chrono::microseconds us);
s64 nsToCycles(std::chrono::nanoseconds ns); s64 nsToCycles(std::chrono::nanoseconds ns);
u64 msToClockCycles(std::chrono::milliseconds ns);
inline std::chrono::milliseconds CyclesToMs(s64 cycles) { u64 usToClockCycles(std::chrono::microseconds ns);
return std::chrono::milliseconds(cycles * 1000 / Hardware::BASE_CLOCK_RATE); u64 nsToClockCycles(std::chrono::nanoseconds ns);
} std::chrono::milliseconds CyclesToMs(s64 cycles);
std::chrono::nanoseconds CyclesToNs(s64 cycles);
inline std::chrono::nanoseconds CyclesToNs(s64 cycles) { std::chrono::microseconds CyclesToUs(s64 cycles);
return std::chrono::nanoseconds(cycles * 1000000000 / Hardware::BASE_CLOCK_RATE);
}
inline std::chrono::microseconds CyclesToUs(s64 cycles) {
return std::chrono::microseconds(cycles * 1000000 / Hardware::BASE_CLOCK_RATE);
}
u64 CpuCyclesToClockCycles(u64 ticks); u64 CpuCyclesToClockCycles(u64 ticks);

@ -2,80 +2,372 @@
// 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 "common/fiber.h"
#include "common/microprofile.h"
#include "common/thread.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/cpu_manager.h" #include "core/cpu_manager.h"
#include "core/gdbstub/gdbstub.h" #include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "video_core/gpu.h"
namespace Core { namespace Core {
CpuManager::CpuManager(System& system) : system{system} {} CpuManager::CpuManager(System& system) : system{system} {}
CpuManager::~CpuManager() = default; CpuManager::~CpuManager() = default;
void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) {
cpu_manager.RunThread(core);
}
void CpuManager::Initialize() { void CpuManager::Initialize() {
for (std::size_t index = 0; index < core_managers.size(); ++index) { running_mode = true;
core_managers[index] = std::make_unique<CoreManager>(system, index); if (is_multicore) {
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
core_data[core].host_thread =
std::make_unique<std::thread>(ThreadStart, std::ref(*this), core);
}
} else {
core_data[0].host_thread = std::make_unique<std::thread>(ThreadStart, std::ref(*this), 0);
} }
} }
void CpuManager::Shutdown() { void CpuManager::Shutdown() {
for (auto& cpu_core : core_managers) { running_mode = false;
cpu_core.reset(); Pause(false);
if (is_multicore) {
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
core_data[core].host_thread->join();
core_data[core].host_thread.reset();
}
} else {
core_data[0].host_thread->join();
core_data[0].host_thread.reset();
} }
} }
CoreManager& CpuManager::GetCoreManager(std::size_t index) { std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
return *core_managers.at(index); return std::function<void(void*)>(GuestThreadFunction);
} }
const CoreManager& CpuManager::GetCoreManager(std::size_t index) const { std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() {
return *core_managers.at(index); return std::function<void(void*)>(IdleThreadFunction);
} }
CoreManager& CpuManager::GetCurrentCoreManager() { std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() {
// Otherwise, use single-threaded mode active_core variable return std::function<void(void*)>(SuspendThreadFunction);
return *core_managers[active_core];
} }
const CoreManager& CpuManager::GetCurrentCoreManager() const { void CpuManager::GuestThreadFunction(void* cpu_manager_) {
// Otherwise, use single-threaded mode active_core variable CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
return *core_managers[active_core]; if (cpu_manager->is_multicore) {
cpu_manager->MultiCoreRunGuestThread();
} else {
cpu_manager->SingleCoreRunGuestThread();
}
} }
void CpuManager::RunLoop(bool tight_loop) { void CpuManager::GuestRewindFunction(void* cpu_manager_) {
if (GDBStub::IsServerEnabled()) { CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
GDBStub::HandlePacket(); if (cpu_manager->is_multicore) {
cpu_manager->MultiCoreRunGuestLoop();
} else {
cpu_manager->SingleCoreRunGuestLoop();
}
}
// If the loop is halted and we want to step, use a tiny (1) number of instructions to void CpuManager::IdleThreadFunction(void* cpu_manager_) {
// execute. Otherwise, get out of the loop function. CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
if (GDBStub::GetCpuHaltFlag()) { if (cpu_manager->is_multicore) {
if (GDBStub::GetCpuStepFlag()) { cpu_manager->MultiCoreRunIdleThread();
tight_loop = false; } else {
} else { cpu_manager->SingleCoreRunIdleThread();
return; }
}
void CpuManager::SuspendThreadFunction(void* cpu_manager_) {
CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
if (cpu_manager->is_multicore) {
cpu_manager->MultiCoreRunSuspendThread();
} else {
cpu_manager->SingleCoreRunSuspendThread();
}
}
void* CpuManager::GetStartFuncParamater() {
return static_cast<void*>(this);
}
///////////////////////////////////////////////////////////////////////////////
/// MultiCore ///
///////////////////////////////////////////////////////////////////////////////
void CpuManager::MultiCoreRunGuestThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
MultiCoreRunGuestLoop();
}
void CpuManager::MultiCoreRunGuestLoop() {
auto& kernel = system.Kernel();
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
auto& arm_interface = thread->ArmInterface();
system.EnterDynarmicProfile();
while (!physical_core->IsInterrupted()) {
arm_interface.Run();
physical_core = &kernel.CurrentPhysicalCore();
}
system.ExitDynarmicProfile();
arm_interface.ClearExclusiveState();
auto& scheduler = kernel.CurrentScheduler();
scheduler.TryDoContextSwitch();
}
}
void CpuManager::MultiCoreRunIdleThread() {
auto& kernel = system.Kernel();
while (true) {
auto& physical_core = kernel.CurrentPhysicalCore();
physical_core.Idle();
auto& scheduler = kernel.CurrentScheduler();
scheduler.TryDoContextSwitch();
}
}
void CpuManager::MultiCoreRunSuspendThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
while (true) {
auto core = kernel.GetCurrentHostThreadID();
auto& scheduler = kernel.CurrentScheduler();
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context);
ASSERT(scheduler.ContextSwitchPending());
ASSERT(core == kernel.GetCurrentHostThreadID());
scheduler.TryDoContextSwitch();
}
}
void CpuManager::MultiCorePause(bool paused) {
if (!paused) {
bool all_not_barrier = false;
while (!all_not_barrier) {
all_not_barrier = true;
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
all_not_barrier &=
!core_data[core].is_running.load() && core_data[core].initialized.load();
} }
} }
} for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
core_data[core].enter_barrier->Set();
auto& core_timing = system.CoreTiming();
core_timing.ResetRun();
bool keep_running{};
do {
keep_running = false;
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
core_timing.SwitchContext(active_core);
if (core_timing.CanCurrentContextRun()) {
core_managers[active_core]->RunLoop(tight_loop);
}
keep_running |= core_timing.CanCurrentContextRun();
} }
} while (keep_running); if (paused_state.load()) {
bool all_barrier = false;
if (GDBStub::IsServerEnabled()) { while (!all_barrier) {
GDBStub::SetCpuStepFlag(false); all_barrier = true;
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
all_barrier &=
core_data[core].is_paused.load() && core_data[core].initialized.load();
}
}
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
core_data[core].exit_barrier->Set();
}
}
} else {
/// Wait until all cores are paused.
bool all_barrier = false;
while (!all_barrier) {
all_barrier = true;
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
all_barrier &=
core_data[core].is_paused.load() && core_data[core].initialized.load();
}
}
/// Don't release the barrier
} }
paused_state = paused;
}
///////////////////////////////////////////////////////////////////////////////
/// SingleCore ///
///////////////////////////////////////////////////////////////////////////////
void CpuManager::SingleCoreRunGuestThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
SingleCoreRunGuestLoop();
}
void CpuManager::SingleCoreRunGuestLoop() {
auto& kernel = system.Kernel();
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
auto& arm_interface = thread->ArmInterface();
system.EnterDynarmicProfile();
if (!physical_core->IsInterrupted()) {
arm_interface.Run();
physical_core = &kernel.CurrentPhysicalCore();
}
system.ExitDynarmicProfile();
thread->SetPhantomMode(true);
system.CoreTiming().Advance();
thread->SetPhantomMode(false);
arm_interface.ClearExclusiveState();
PreemptSingleCore();
auto& scheduler = kernel.Scheduler(current_core);
scheduler.TryDoContextSwitch();
}
}
void CpuManager::SingleCoreRunIdleThread() {
auto& kernel = system.Kernel();
while (true) {
auto& physical_core = kernel.CurrentPhysicalCore();
PreemptSingleCore(false);
system.CoreTiming().AddTicks(1000U);
idle_count++;
auto& scheduler = physical_core.Scheduler();
scheduler.TryDoContextSwitch();
}
}
void CpuManager::SingleCoreRunSuspendThread() {
auto& kernel = system.Kernel();
{
auto& sched = kernel.CurrentScheduler();
sched.OnThreadStart();
}
while (true) {
auto core = kernel.GetCurrentHostThreadID();
auto& scheduler = kernel.CurrentScheduler();
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context);
ASSERT(scheduler.ContextSwitchPending());
ASSERT(core == kernel.GetCurrentHostThreadID());
scheduler.TryDoContextSwitch();
}
}
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
std::size_t old_core = current_core;
auto& scheduler = system.Kernel().Scheduler(old_core);
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
if (idle_count >= 4 || from_running_enviroment) {
if (!from_running_enviroment) {
system.CoreTiming().Idle();
idle_count = 0;
}
current_thread->SetPhantomMode(true);
system.CoreTiming().Advance();
current_thread->SetPhantomMode(false);
}
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
system.CoreTiming().ResetTicks();
scheduler.Unload();
auto& next_scheduler = system.Kernel().Scheduler(current_core);
Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
/// May have changed scheduler
auto& current_scheduler = system.Kernel().Scheduler(current_core);
current_scheduler.Reload();
auto* currrent_thread2 = current_scheduler.GetCurrentThread();
if (!currrent_thread2->IsIdleThread()) {
idle_count = 0;
}
}
void CpuManager::SingleCorePause(bool paused) {
if (!paused) {
bool all_not_barrier = false;
while (!all_not_barrier) {
all_not_barrier = !core_data[0].is_running.load() && core_data[0].initialized.load();
}
core_data[0].enter_barrier->Set();
if (paused_state.load()) {
bool all_barrier = false;
while (!all_barrier) {
all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
}
core_data[0].exit_barrier->Set();
}
} else {
/// Wait until all cores are paused.
bool all_barrier = false;
while (!all_barrier) {
all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
}
/// Don't release the barrier
}
paused_state = paused;
}
void CpuManager::Pause(bool paused) {
if (is_multicore) {
MultiCorePause(paused);
} else {
SingleCorePause(paused);
}
}
void CpuManager::RunThread(std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
name = "yuzu:CoreCPUThread_" + std::to_string(core);
} else {
name = "yuzu:CPUThread";
}
MicroProfileOnThreadCreate(name.c_str());
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
auto& data = core_data[core];
data.enter_barrier = std::make_unique<Common::Event>();
data.exit_barrier = std::make_unique<Common::Event>();
data.host_context = Common::Fiber::ThreadToFiber();
data.is_running = false;
data.initialized = true;
const bool sc_sync = !is_async_gpu && !is_multicore;
bool sc_sync_first_use = sc_sync;
/// Running
while (running_mode) {
data.is_running = false;
data.enter_barrier->Wait();
if (sc_sync_first_use) {
system.GPU().ObtainContext();
sc_sync_first_use = false;
}
auto& scheduler = system.Kernel().CurrentScheduler();
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
data.is_running = true;
Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext());
data.is_running = false;
data.is_paused = true;
data.exit_barrier->Wait();
data.is_paused = false;
}
/// Time to cleanup
data.host_context->Exit();
data.enter_barrier.reset();
data.exit_barrier.reset();
data.initialized = false;
} }
} // namespace Core } // namespace Core

@ -5,12 +5,19 @@
#pragma once #pragma once
#include <array> #include <array>
#include <atomic>
#include <functional>
#include <memory> #include <memory>
#include <thread>
#include "core/hardware_properties.h" #include "core/hardware_properties.h"
namespace Common {
class Event;
class Fiber;
} // namespace Common
namespace Core { namespace Core {
class CoreManager;
class System; class System;
class CpuManager { class CpuManager {
@ -24,24 +31,75 @@ public:
CpuManager& operator=(const CpuManager&) = delete; CpuManager& operator=(const CpuManager&) = delete;
CpuManager& operator=(CpuManager&&) = delete; CpuManager& operator=(CpuManager&&) = delete;
/// Sets if emulation is multicore or single core, must be set before Initialize
void SetMulticore(bool is_multicore) {
this->is_multicore = is_multicore;
}
/// Sets if emulation is using an asynchronous GPU.
void SetAsyncGpu(bool is_async_gpu) {
this->is_async_gpu = is_async_gpu;
}
void Initialize(); void Initialize();
void Shutdown(); void Shutdown();
CoreManager& GetCoreManager(std::size_t index); void Pause(bool paused);
const CoreManager& GetCoreManager(std::size_t index) const;
CoreManager& GetCurrentCoreManager(); std::function<void(void*)> GetGuestThreadStartFunc();
const CoreManager& GetCurrentCoreManager() const; std::function<void(void*)> GetIdleThreadStartFunc();
std::function<void(void*)> GetSuspendThreadStartFunc();
void* GetStartFuncParamater();
std::size_t GetActiveCoreIndex() const { void PreemptSingleCore(bool from_running_enviroment = true);
return active_core;
std::size_t CurrentCore() const {
return current_core.load();
} }
void RunLoop(bool tight_loop);
private: private:
std::array<std::unique_ptr<CoreManager>, Hardware::NUM_CPU_CORES> core_managers; static void GuestThreadFunction(void* cpu_manager);
std::size_t active_core{}; ///< Active core, only used in single thread mode static void GuestRewindFunction(void* cpu_manager);
static void IdleThreadFunction(void* cpu_manager);
static void SuspendThreadFunction(void* cpu_manager);
void MultiCoreRunGuestThread();
void MultiCoreRunGuestLoop();
void MultiCoreRunIdleThread();
void MultiCoreRunSuspendThread();
void MultiCorePause(bool paused);
void SingleCoreRunGuestThread();
void SingleCoreRunGuestLoop();
void SingleCoreRunIdleThread();
void SingleCoreRunSuspendThread();
void SingleCorePause(bool paused);
static void ThreadStart(CpuManager& cpu_manager, std::size_t core);
void RunThread(std::size_t core);
struct CoreData {
std::shared_ptr<Common::Fiber> host_context;
std::unique_ptr<Common::Event> enter_barrier;
std::unique_ptr<Common::Event> exit_barrier;
std::atomic<bool> is_running;
std::atomic<bool> is_paused;
std::atomic<bool> initialized;
std::unique_ptr<std::thread> host_thread;
};
std::atomic<bool> running_mode{};
std::atomic<bool> paused_state{};
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
bool is_async_gpu{};
bool is_multicore{};
std::atomic<std::size_t> current_core{};
std::size_t preemption_count{};
std::size_t idle_count{};
static constexpr std::size_t max_cycle_runs = 5;
System& system; System& system;
}; };

@ -223,7 +223,16 @@ bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
class KeyManager { class KeyManager {
public: public:
KeyManager(); static KeyManager& Instance() {
static KeyManager instance;
return instance;
}
KeyManager(const KeyManager&) = delete;
KeyManager& operator=(const KeyManager&) = delete;
KeyManager(KeyManager&&) = delete;
KeyManager& operator=(KeyManager&&) = delete;
bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
@ -257,6 +266,8 @@ public:
bool AddTicketPersonalized(Ticket raw); bool AddTicketPersonalized(Ticket raw);
private: private:
KeyManager();
std::map<KeyIndex<S128KeyType>, Key128> s128_keys; std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::map<KeyIndex<S256KeyType>, Key256> s256_keys; std::map<KeyIndex<S256KeyType>, Key256> s256_keys;

@ -79,7 +79,7 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
} }
VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
Core::Crypto::KeyManager keys; auto& keys = Core::Crypto::KeyManager::Instance();
Core::Crypto::PartitionDataManager pdm{ Core::Crypto::PartitionDataManager pdm{
Core::System::GetInstance().GetFilesystem()->OpenDirectory( Core::System::GetInstance().GetFilesystem()->OpenDirectory(
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)}; FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};

@ -178,7 +178,7 @@ u32 XCI::GetSystemUpdateVersion() {
return 0; return 0;
for (const auto& file : update->GetFiles()) { for (const auto& file : update->GetFiles()) {
NCA nca{file, nullptr, 0, keys}; NCA nca{file, nullptr, 0};
if (nca.GetStatus() != Loader::ResultStatus::Success) if (nca.GetStatus() != Loader::ResultStatus::Success)
continue; continue;
@ -286,7 +286,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
continue; continue;
} }
auto nca = std::make_shared<NCA>(file, nullptr, 0, keys); auto nca = std::make_shared<NCA>(file, nullptr, 0);
if (nca->IsUpdate()) { if (nca->IsUpdate()) {
continue; continue;
} }

@ -140,6 +140,6 @@ private:
u64 update_normal_partition_end; u64 update_normal_partition_end;
Core::Crypto::KeyManager keys; Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
}; };
} // namespace FileSys } // namespace FileSys

@ -118,9 +118,8 @@ static bool IsValidNCA(const NCAHeader& header) {
return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
} }
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset, NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
Core::Crypto::KeyManager keys_) : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
: file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)), keys(std::move(keys_)) {
if (file == nullptr) { if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile; status = Loader::ResultStatus::ErrorNullFile;
return; return;

@ -99,8 +99,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
class NCA : public ReadOnlyVfsDirectory { class NCA : public ReadOnlyVfsDirectory {
public: public:
explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
u64 bktr_base_ivfc_offset = 0, u64 bktr_base_ivfc_offset = 0);
Core::Crypto::KeyManager keys = Core::Crypto::KeyManager());
~NCA() override; ~NCA() override;
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;
@ -159,7 +158,7 @@ private:
bool encrypted = false; bool encrypted = false;
bool is_update = false; bool is_update = false;
Core::Crypto::KeyManager keys; Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
}; };
} // namespace FileSys } // namespace FileSys

@ -408,7 +408,7 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
if (file == nullptr) if (file == nullptr)
continue; continue;
const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0, keys); const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0);
if (nca->GetStatus() != Loader::ResultStatus::Success || if (nca->GetStatus() != Loader::ResultStatus::Success ||
nca->GetType() != NCAContentType::Meta) { nca->GetType() != NCAContentType::Meta) {
continue; continue;
@ -486,7 +486,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
const auto raw = GetEntryRaw(title_id, type); const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr) if (raw == nullptr)
return nullptr; return nullptr;
return std::make_unique<NCA>(raw, nullptr, 0, keys); return std::make_unique<NCA>(raw, nullptr, 0);
} }
template <typename T> template <typename T>
@ -865,7 +865,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord
const auto res = GetEntryRaw(title_id, type); const auto res = GetEntryRaw(title_id, type);
if (res == nullptr) if (res == nullptr)
return nullptr; return nullptr;
return std::make_unique<NCA>(res, nullptr, 0, keys); return std::make_unique<NCA>(res, nullptr, 0);
} }
std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(

@ -88,7 +88,7 @@ public:
protected: protected:
// A single instance of KeyManager to be used by GetEntry() // A single instance of KeyManager to be used by GetEntry()
Core::Crypto::KeyManager keys; Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
}; };
class PlaceholderCache { class PlaceholderCache {

@ -21,7 +21,7 @@
namespace FileSys { namespace FileSys {
namespace { namespace {
void SetTicketKeys(const std::vector<VirtualFile>& files) { void SetTicketKeys(const std::vector<VirtualFile>& files) {
Core::Crypto::KeyManager keys; auto& keys = Core::Crypto::KeyManager::Instance();
for (const auto& ticket_file : files) { for (const auto& ticket_file : files) {
if (ticket_file == nullptr) { if (ticket_file == nullptr) {
@ -285,7 +285,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
continue; continue;
} }
auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys); auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
if (next_nca->GetType() == NCAContentType::Program) { if (next_nca->GetType() == NCAContentType::Program) {
program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
} }

@ -73,7 +73,7 @@ private:
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas; std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files; std::vector<VirtualFile> ticket_files;
Core::Crypto::KeyManager keys; Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
VirtualFile romfs; VirtualFile romfs;
VirtualDir exefs; VirtualDir exefs;

@ -62,6 +62,6 @@ private:
VirtualFile dec_file; VirtualFile dec_file;
Core::Crypto::KeyManager keys; Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
}; };
} // namespace FileSys } // namespace FileSys

@ -35,7 +35,6 @@
#include "common/swap.h" #include "common/swap.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_manager.h"
#include "core/gdbstub/gdbstub.h" #include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"

@ -42,6 +42,10 @@ struct EmuThreadHandle {
constexpr u32 invalid_handle = 0xFFFFFFFF; constexpr u32 invalid_handle = 0xFFFFFFFF;
return {invalid_handle, invalid_handle}; return {invalid_handle, invalid_handle};
} }
bool IsInvalid() const {
return (*this) == InvalidHandle();
}
}; };
} // namespace Core } // namespace Core

@ -7,11 +7,15 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h" #include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/memory.h" #include "core/memory.h"
@ -20,6 +24,7 @@ namespace Kernel {
// Wake up num_to_wake (or all) threads in a vector. // Wake up num_to_wake (or all) threads in a vector.
void AddressArbiter::WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, void AddressArbiter::WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads,
s32 num_to_wake) { s32 num_to_wake) {
auto& time_manager = system.Kernel().TimeManager();
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process // Only process up to 'target' threads, unless 'target' is <= 0, in which case process
// them all. // them all.
std::size_t last = waiting_threads.size(); std::size_t last = waiting_threads.size();
@ -29,12 +34,10 @@ void AddressArbiter::WakeThreads(const std::vector<std::shared_ptr<Thread>>& wai
// Signal the waiting threads. // Signal the waiting threads.
for (std::size_t i = 0; i < last; i++) { for (std::size_t i = 0; i < last; i++) {
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb); waiting_threads[i]->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
RemoveThread(waiting_threads[i]); RemoveThread(waiting_threads[i]);
waiting_threads[i]->SetArbiterWaitAddress(0); waiting_threads[i]->WaitForArbitration(false);
waiting_threads[i]->ResumeFromWait(); waiting_threads[i]->ResumeFromWait();
system.PrepareReschedule(waiting_threads[i]->GetProcessorID());
} }
} }
@ -56,6 +59,7 @@ ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 v
} }
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) { ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
SchedulerLock lock(system.Kernel());
const std::vector<std::shared_ptr<Thread>> waiting_threads = const std::vector<std::shared_ptr<Thread>> waiting_threads =
GetThreadsWaitingOnAddress(address); GetThreadsWaitingOnAddress(address);
WakeThreads(waiting_threads, num_to_wake); WakeThreads(waiting_threads, num_to_wake);
@ -64,6 +68,7 @@ ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
s32 num_to_wake) { s32 num_to_wake) {
SchedulerLock lock(system.Kernel());
auto& memory = system.Memory(); auto& memory = system.Memory();
// Ensure that we can write to the address. // Ensure that we can write to the address.
@ -71,16 +76,24 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
return ERR_INVALID_ADDRESS_STATE; return ERR_INVALID_ADDRESS_STATE;
} }
if (static_cast<s32>(memory.Read32(address)) != value) { const std::size_t current_core = system.CurrentCoreIndex();
return ERR_INVALID_STATE; auto& monitor = system.Monitor();
} u32 current_value;
do {
current_value = monitor.ExclusiveRead32(current_core, address);
if (current_value != value) {
return ERR_INVALID_STATE;
}
current_value++;
} while (!monitor.ExclusiveWrite32(current_core, address, current_value));
memory.Write32(address, static_cast<u32>(value + 1));
return SignalToAddressOnly(address, num_to_wake); return SignalToAddressOnly(address, num_to_wake);
} }
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
s32 num_to_wake) { s32 num_to_wake) {
SchedulerLock lock(system.Kernel());
auto& memory = system.Memory(); auto& memory = system.Memory();
// Ensure that we can write to the address. // Ensure that we can write to the address.
@ -92,29 +105,33 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
const std::vector<std::shared_ptr<Thread>> waiting_threads = const std::vector<std::shared_ptr<Thread>> waiting_threads =
GetThreadsWaitingOnAddress(address); GetThreadsWaitingOnAddress(address);
// Determine the modified value depending on the waiting count. const std::size_t current_core = system.CurrentCoreIndex();
auto& monitor = system.Monitor();
s32 updated_value; s32 updated_value;
if (num_to_wake <= 0) { do {
if (waiting_threads.empty()) { updated_value = monitor.ExclusiveRead32(current_core, address);
updated_value = value + 1;
} else {
updated_value = value - 1;
}
} else {
if (waiting_threads.empty()) {
updated_value = value + 1;
} else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
updated_value = value - 1;
} else {
updated_value = value;
}
}
if (static_cast<s32>(memory.Read32(address)) != value) { if (updated_value != value) {
return ERR_INVALID_STATE; return ERR_INVALID_STATE;
} }
// Determine the modified value depending on the waiting count.
if (num_to_wake <= 0) {
if (waiting_threads.empty()) {
updated_value = value + 1;
} else {
updated_value = value - 1;
}
} else {
if (waiting_threads.empty()) {
updated_value = value + 1;
} else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
updated_value = value - 1;
} else {
updated_value = value;
}
}
} while (!monitor.ExclusiveWrite32(current_core, address, updated_value));
memory.Write32(address, static_cast<u32>(updated_value));
WakeThreads(waiting_threads, num_to_wake); WakeThreads(waiting_threads, num_to_wake);
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
@ -136,60 +153,127 @@ ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s
ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
bool should_decrement) { bool should_decrement) {
auto& memory = system.Memory(); auto& memory = system.Memory();
auto& kernel = system.Kernel();
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
// Ensure that we can read the address. Handle event_handle = InvalidHandle;
if (!memory.IsValidVirtualAddress(address)) { {
return ERR_INVALID_ADDRESS_STATE; SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
if (current_thread->IsPendingTermination()) {
lock.CancelSleep();
return ERR_THREAD_TERMINATING;
}
// Ensure that we can read the address.
if (!memory.IsValidVirtualAddress(address)) {
lock.CancelSleep();
return ERR_INVALID_ADDRESS_STATE;
}
s32 current_value = static_cast<s32>(memory.Read32(address));
if (current_value >= value) {
lock.CancelSleep();
return ERR_INVALID_STATE;
}
current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
s32 decrement_value;
const std::size_t current_core = system.CurrentCoreIndex();
auto& monitor = system.Monitor();
do {
current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address));
if (should_decrement) {
decrement_value = current_value - 1;
} else {
decrement_value = current_value;
}
} while (
!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value)));
// Short-circuit without rescheduling, if timeout is zero.
if (timeout == 0) {
lock.CancelSleep();
return RESULT_TIMEOUT;
}
current_thread->SetArbiterWaitAddress(address);
InsertThread(SharedFrom(current_thread));
current_thread->SetStatus(ThreadStatus::WaitArb);
current_thread->WaitForArbitration(true);
} }
const s32 cur_value = static_cast<s32>(memory.Read32(address)); if (event_handle != InvalidHandle) {
if (cur_value >= value) { auto& time_manager = kernel.TimeManager();
return ERR_INVALID_STATE; time_manager.UnscheduleTimeEvent(event_handle);
} }
if (should_decrement) { {
memory.Write32(address, static_cast<u32>(cur_value - 1)); SchedulerLock lock(kernel);
if (current_thread->IsWaitingForArbitration()) {
RemoveThread(SharedFrom(current_thread));
current_thread->WaitForArbitration(false);
}
} }
// Short-circuit without rescheduling, if timeout is zero. return current_thread->GetSignalingResult();
if (timeout == 0) {
return RESULT_TIMEOUT;
}
return WaitForAddressImpl(address, timeout);
} }
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) { ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
auto& memory = system.Memory(); auto& memory = system.Memory();
auto& kernel = system.Kernel();
// Ensure that we can read the address.
if (!memory.IsValidVirtualAddress(address)) {
return ERR_INVALID_ADDRESS_STATE;
}
// Only wait for the address if equal.
if (static_cast<s32>(memory.Read32(address)) != value) {
return ERR_INVALID_STATE;
}
// Short-circuit without rescheduling if timeout is zero.
if (timeout == 0) {
return RESULT_TIMEOUT;
}
return WaitForAddressImpl(address, timeout);
}
ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
current_thread->SetArbiterWaitAddress(address);
InsertThread(SharedFrom(current_thread));
current_thread->SetStatus(ThreadStatus::WaitArb);
current_thread->InvalidateWakeupCallback();
current_thread->WakeAfterDelay(timeout);
system.PrepareReschedule(current_thread->GetProcessorID()); Handle event_handle = InvalidHandle;
return RESULT_TIMEOUT; {
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
if (current_thread->IsPendingTermination()) {
lock.CancelSleep();
return ERR_THREAD_TERMINATING;
}
// Ensure that we can read the address.
if (!memory.IsValidVirtualAddress(address)) {
lock.CancelSleep();
return ERR_INVALID_ADDRESS_STATE;
}
s32 current_value = static_cast<s32>(memory.Read32(address));
if (current_value != value) {
lock.CancelSleep();
return ERR_INVALID_STATE;
}
// Short-circuit without rescheduling, if timeout is zero.
if (timeout == 0) {
lock.CancelSleep();
return RESULT_TIMEOUT;
}
current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
current_thread->SetArbiterWaitAddress(address);
InsertThread(SharedFrom(current_thread));
current_thread->SetStatus(ThreadStatus::WaitArb);
current_thread->WaitForArbitration(true);
}
if (event_handle != InvalidHandle) {
auto& time_manager = kernel.TimeManager();
time_manager.UnscheduleTimeEvent(event_handle);
}
{
SchedulerLock lock(kernel);
if (current_thread->IsWaitingForArbitration()) {
RemoveThread(SharedFrom(current_thread));
current_thread->WaitForArbitration(false);
}
}
return current_thread->GetSignalingResult();
} }
void AddressArbiter::HandleWakeupThread(std::shared_ptr<Thread> thread) { void AddressArbiter::HandleWakeupThread(std::shared_ptr<Thread> thread) {
@ -221,9 +305,9 @@ void AddressArbiter::RemoveThread(std::shared_ptr<Thread> thread) {
const auto iter = std::find_if(thread_list.cbegin(), thread_list.cend(), const auto iter = std::find_if(thread_list.cbegin(), thread_list.cend(),
[&thread](const auto& entry) { return thread == entry; }); [&thread](const auto& entry) { return thread == entry; });
ASSERT(iter != thread_list.cend()); if (iter != thread_list.cend()) {
thread_list.erase(iter);
thread_list.erase(iter); }
} }
std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress( std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(

@ -73,9 +73,6 @@ private:
/// Waits on an address if the value passed is equal to the argument value. /// Waits on an address if the value passed is equal to the argument value.
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout); ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
// Waits on the given address with a timeout in nanoseconds
ResultCode WaitForAddressImpl(VAddr address, s64 timeout);
/// Wake up num_to_wake (or all) threads in a vector. /// Wake up num_to_wake (or all) threads in a vector.
void WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, s32 num_to_wake); void WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, s32 num_to_wake);

@ -34,7 +34,7 @@ ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() {
} }
// Wake the threads waiting on the ServerPort // Wake the threads waiting on the ServerPort
server_port->WakeupAllWaitingThreads(); server_port->Signal();
return MakeResult(std::move(client)); return MakeResult(std::move(client));
} }

@ -12,6 +12,7 @@ namespace Kernel {
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7}; constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14}; constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
constexpr ResultCode ERR_THREAD_TERMINATING{ErrorModule::Kernel, 59};
constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101}; constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102}; constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
constexpr ResultCode ERR_OUT_OF_RESOURCES{ErrorModule::Kernel, 103}; constexpr ResultCode ERR_OUT_OF_RESOURCES{ErrorModule::Kernel, 103};

@ -14,14 +14,17 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h" #include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h" #include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/kernel/writable_event.h" #include "core/hle/kernel/writable_event.h"
#include "core/memory.h" #include "core/memory.h"
@ -46,15 +49,6 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
const std::string& reason, u64 timeout, WakeupCallback&& callback, const std::string& reason, u64 timeout, WakeupCallback&& callback,
std::shared_ptr<WritableEvent> writable_event) { std::shared_ptr<WritableEvent> writable_event) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires. // Put the client thread to sleep until the wait event is signaled or the timeout expires.
thread->SetWakeupCallback(
[context = *this, callback](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<SynchronizationObject> object,
std::size_t index) mutable -> bool {
ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
callback(thread, context, reason);
context.WriteToOutgoingCommandBuffer(*thread);
return true;
});
if (!writable_event) { if (!writable_event) {
// Create event if not provided // Create event if not provided
@ -62,14 +56,26 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
writable_event = pair.writable; writable_event = pair.writable;
} }
const auto readable_event{writable_event->GetReadableEvent()}; {
writable_event->Clear(); Handle event_handle = InvalidHandle;
thread->SetStatus(ThreadStatus::WaitHLEEvent); SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
thread->SetSynchronizationObjects({readable_event}); thread->SetHLECallback(
readable_event->AddWaitingThread(thread); [context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool {
ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT
if (timeout > 0) { ? ThreadWakeupReason::Timeout
thread->WakeAfterDelay(timeout); : ThreadWakeupReason::Signal;
callback(thread, context, reason);
context.WriteToOutgoingCommandBuffer(*thread);
return true;
});
const auto readable_event{writable_event->GetReadableEvent()};
writable_event->Clear();
thread->SetHLESyncObject(readable_event.get());
thread->SetStatus(ThreadStatus::WaitHLEEvent);
thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
readable_event->AddWaitingThread(thread);
lock.Release();
thread->SetHLETimeEvent(event_handle);
} }
is_thread_waiting = true; is_thread_waiting = true;
@ -282,18 +288,18 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
} }
std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
std::vector<u8> buffer; std::vector<u8> buffer{};
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()}; BufferDescriptorA()[buffer_index].Size()};
if (is_buffer_a) { if (is_buffer_a) {
ASSERT_MSG(BufferDescriptorA().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorA().size() > buffer_index, { return buffer; },
"BufferDescriptorA invalid buffer_index {}", buffer_index); "BufferDescriptorA invalid buffer_index {}", buffer_index);
buffer.resize(BufferDescriptorA()[buffer_index].Size()); buffer.resize(BufferDescriptorA()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size()); memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size());
} else { } else {
ASSERT_MSG(BufferDescriptorX().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorX().size() > buffer_index, { return buffer; },
"BufferDescriptorX invalid buffer_index {}", buffer_index); "BufferDescriptorX invalid buffer_index {}", buffer_index);
buffer.resize(BufferDescriptorX()[buffer_index].Size()); buffer.resize(BufferDescriptorX()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size()); memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size());
} }
@ -318,16 +324,16 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
} }
if (is_buffer_b) { if (is_buffer_b) {
ASSERT_MSG(BufferDescriptorB().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorB().size() > buffer_index &&
"BufferDescriptorB invalid buffer_index {}", buffer_index); BufferDescriptorB()[buffer_index].Size() >= size,
ASSERT_MSG(BufferDescriptorB()[buffer_index].Size() >= size, { return 0; }, "BufferDescriptorB is invalid, index={}, size={}",
"BufferDescriptorB buffer_index {} is not large enough", buffer_index); buffer_index, size);
memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
} else { } else {
ASSERT_MSG(BufferDescriptorC().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorC().size() > buffer_index &&
"BufferDescriptorC invalid buffer_index {}", buffer_index); BufferDescriptorC()[buffer_index].Size() >= size,
ASSERT_MSG(BufferDescriptorC()[buffer_index].Size() >= size, { return 0; }, "BufferDescriptorC is invalid, index={}, size={}",
"BufferDescriptorC buffer_index {} is not large enough", buffer_index); buffer_index, size);
memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
} }
@ -338,16 +344,12 @@ std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()}; BufferDescriptorA()[buffer_index].Size()};
if (is_buffer_a) { if (is_buffer_a) {
ASSERT_MSG(BufferDescriptorA().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorA().size() > buffer_index, { return 0; },
"BufferDescriptorA invalid buffer_index {}", buffer_index); "BufferDescriptorA invalid buffer_index {}", buffer_index);
ASSERT_MSG(BufferDescriptorA()[buffer_index].Size() > 0,
"BufferDescriptorA buffer_index {} is empty", buffer_index);
return BufferDescriptorA()[buffer_index].Size(); return BufferDescriptorA()[buffer_index].Size();
} else { } else {
ASSERT_MSG(BufferDescriptorX().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorX().size() > buffer_index, { return 0; },
"BufferDescriptorX invalid buffer_index {}", buffer_index); "BufferDescriptorX invalid buffer_index {}", buffer_index);
ASSERT_MSG(BufferDescriptorX()[buffer_index].Size() > 0,
"BufferDescriptorX buffer_index {} is empty", buffer_index);
return BufferDescriptorX()[buffer_index].Size(); return BufferDescriptorX()[buffer_index].Size();
} }
} }
@ -356,14 +358,15 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index && const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
BufferDescriptorB()[buffer_index].Size()}; BufferDescriptorB()[buffer_index].Size()};
if (is_buffer_b) { if (is_buffer_b) {
ASSERT_MSG(BufferDescriptorB().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorB().size() > buffer_index, { return 0; },
"BufferDescriptorB invalid buffer_index {}", buffer_index); "BufferDescriptorB invalid buffer_index {}", buffer_index);
return BufferDescriptorB()[buffer_index].Size(); return BufferDescriptorB()[buffer_index].Size();
} else { } else {
ASSERT_MSG(BufferDescriptorC().size() > buffer_index, ASSERT_OR_EXECUTE_MSG(BufferDescriptorC().size() > buffer_index, { return 0; },
"BufferDescriptorC invalid buffer_index {}", buffer_index); "BufferDescriptorC invalid buffer_index {}", buffer_index);
return BufferDescriptorC()[buffer_index].Size(); return BufferDescriptorC()[buffer_index].Size();
} }
return 0;
} }
std::string HLERequestContext::Description() const { std::string HLERequestContext::Description() const {

@ -2,6 +2,7 @@
// 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 <array>
#include <atomic> #include <atomic>
#include <bitset> #include <bitset>
#include <functional> #include <functional>
@ -13,11 +14,15 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/thread.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/cpu_manager.h"
#include "core/device_memory.h" #include "core/device_memory.h"
#include "core/hardware_properties.h" #include "core/hardware_properties.h"
#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_port.h"
@ -39,85 +44,28 @@
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/memory.h" #include "core/memory.h"
MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
namespace Kernel { namespace Kernel {
/**
* Callback that will wake up the thread it was scheduled for
* @param thread_handle The handle of the thread that's been awoken
* @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
*/
static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_late) {
const auto proper_handle = static_cast<Handle>(thread_handle);
const auto& system = Core::System::GetInstance();
// Lock the global kernel mutex when we enter the kernel HLE.
std::lock_guard lock{HLE::g_hle_lock};
std::shared_ptr<Thread> thread =
system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
if (thread == nullptr) {
LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
return;
}
bool resume = true;
if (thread->GetStatus() == ThreadStatus::WaitSynch ||
thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
// Remove the thread from each of its waiting objects' waitlists
for (const auto& object : thread->GetSynchronizationObjects()) {
object->RemoveWaitingThread(thread);
}
thread->ClearSynchronizationObjects();
// Invoke the wakeup callback before clearing the wait objects
if (thread->HasWakeupCallback()) {
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
}
} else if (thread->GetStatus() == ThreadStatus::WaitMutex ||
thread->GetStatus() == ThreadStatus::WaitCondVar) {
thread->SetMutexWaitAddress(0);
thread->SetWaitHandle(0);
if (thread->GetStatus() == ThreadStatus::WaitCondVar) {
thread->GetOwnerProcess()->RemoveConditionVariableThread(thread);
thread->SetCondVarWaitAddress(0);
}
auto* const lock_owner = thread->GetLockOwner();
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
// and don't have a lock owner unless SignalProcessWideKey was called first and the thread
// wasn't awakened due to the mutex already being acquired.
if (lock_owner != nullptr) {
lock_owner->RemoveMutexWaiter(thread);
}
}
if (thread->GetStatus() == ThreadStatus::WaitArb) {
auto& address_arbiter = thread->GetOwnerProcess()->GetAddressArbiter();
address_arbiter.HandleWakeupThread(thread);
}
if (resume) {
if (thread->GetStatus() == ThreadStatus::WaitCondVar ||
thread->GetStatus() == ThreadStatus::WaitArb) {
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
}
thread->ResumeFromWait();
}
}
struct KernelCore::Impl { struct KernelCore::Impl {
explicit Impl(Core::System& system, KernelCore& kernel) explicit Impl(Core::System& system, KernelCore& kernel)
: global_scheduler{kernel}, synchronization{system}, time_manager{system}, system{system} {} : global_scheduler{kernel}, synchronization{system}, time_manager{system}, system{system} {}
void SetMulticore(bool is_multicore) {
this->is_multicore = is_multicore;
}
void Initialize(KernelCore& kernel) { void Initialize(KernelCore& kernel) {
Shutdown(); Shutdown();
RegisterHostThread();
InitializePhysicalCores(); InitializePhysicalCores();
InitializeSystemResourceLimit(kernel); InitializeSystemResourceLimit(kernel);
InitializeMemoryLayout(); InitializeMemoryLayout();
InitializeThreads(); InitializePreemption(kernel);
InitializePreemption(); InitializeSchedulers();
InitializeSuspendThreads();
} }
void Shutdown() { void Shutdown() {
@ -126,13 +74,26 @@ struct KernelCore::Impl {
next_user_process_id = Process::ProcessIDMin; next_user_process_id = Process::ProcessIDMin;
next_thread_id = 1; next_thread_id = 1;
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
if (suspend_threads[i]) {
suspend_threads[i].reset();
}
}
for (std::size_t i = 0; i < cores.size(); i++) {
cores[i].Shutdown();
schedulers[i].reset();
}
cores.clear();
registered_core_threads.reset();
process_list.clear(); process_list.clear();
current_process = nullptr; current_process = nullptr;
system_resource_limit = nullptr; system_resource_limit = nullptr;
global_handle_table.Clear(); global_handle_table.Clear();
thread_wakeup_event_type = nullptr;
preemption_event = nullptr; preemption_event = nullptr;
global_scheduler.Shutdown(); global_scheduler.Shutdown();
@ -145,13 +106,21 @@ struct KernelCore::Impl {
cores.clear(); cores.clear();
exclusive_monitor.reset(); exclusive_monitor.reset();
host_thread_ids.clear();
} }
void InitializePhysicalCores() { void InitializePhysicalCores() {
exclusive_monitor = exclusive_monitor =
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
cores.emplace_back(system, i, *exclusive_monitor); schedulers[i] = std::make_unique<Kernel::Scheduler>(system, i);
cores.emplace_back(system, i, *schedulers[i], interrupts[i]);
}
}
void InitializeSchedulers() {
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
cores[i].Scheduler().Initialize();
} }
} }
@ -173,15 +142,13 @@ struct KernelCore::Impl {
} }
} }
void InitializeThreads() { void InitializePreemption(KernelCore& kernel) {
thread_wakeup_event_type = preemption_event = Core::Timing::CreateEvent(
Core::Timing::CreateEvent("ThreadWakeupCallback", ThreadWakeupCallback); "PreemptionCallback", [this, &kernel](u64 userdata, s64 cycles_late) {
} {
SchedulerLock lock(kernel);
void InitializePreemption() { global_scheduler.PreemptThreads();
preemption_event = }
Core::Timing::CreateEvent("PreemptionCallback", [this](u64 userdata, s64 cycles_late) {
global_scheduler.PreemptThreads();
s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10)); s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
system.CoreTiming().ScheduleEvent(time_interval, preemption_event); system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
}); });
@ -190,6 +157,20 @@ struct KernelCore::Impl {
system.CoreTiming().ScheduleEvent(time_interval, preemption_event); system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
} }
void InitializeSuspendThreads() {
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
std::string name = "Suspend Thread Id:" + std::to_string(i);
std::function<void(void*)> init_func =
system.GetCpuManager().GetSuspendThreadStartFunc();
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
ThreadType type =
static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_SUSPEND);
auto thread_res = Thread::Create(system, type, name, 0, 0, 0, static_cast<u32>(i), 0,
nullptr, std::move(init_func), init_func_parameter);
suspend_threads[i] = std::move(thread_res).Unwrap();
}
}
void MakeCurrentProcess(Process* process) { void MakeCurrentProcess(Process* process) {
current_process = process; current_process = process;
@ -197,15 +178,17 @@ struct KernelCore::Impl {
return; return;
} }
for (auto& core : cores) { u32 core_id = GetCurrentHostThreadID();
core.SetIs64Bit(process->Is64BitProcess()); if (core_id < Core::Hardware::NUM_CPU_CORES) {
system.Memory().SetCurrentPageTable(*process, core_id);
} }
system.Memory().SetCurrentPageTable(*process);
} }
void RegisterCoreThread(std::size_t core_id) { void RegisterCoreThread(std::size_t core_id) {
std::unique_lock lock{register_thread_mutex}; std::unique_lock lock{register_thread_mutex};
if (!is_multicore) {
single_core_thread_id = std::this_thread::get_id();
}
const std::thread::id this_id = std::this_thread::get_id(); const std::thread::id this_id = std::this_thread::get_id();
const auto it = host_thread_ids.find(this_id); const auto it = host_thread_ids.find(this_id);
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
@ -219,12 +202,19 @@ struct KernelCore::Impl {
std::unique_lock lock{register_thread_mutex}; std::unique_lock lock{register_thread_mutex};
const std::thread::id this_id = std::this_thread::get_id(); const std::thread::id this_id = std::this_thread::get_id();
const auto it = host_thread_ids.find(this_id); const auto it = host_thread_ids.find(this_id);
ASSERT(it == host_thread_ids.end()); if (it != host_thread_ids.end()) {
return;
}
host_thread_ids[this_id] = registered_thread_ids++; host_thread_ids[this_id] = registered_thread_ids++;
} }
u32 GetCurrentHostThreadID() const { u32 GetCurrentHostThreadID() const {
const std::thread::id this_id = std::this_thread::get_id(); const std::thread::id this_id = std::this_thread::get_id();
if (!is_multicore) {
if (single_core_thread_id == this_id) {
return static_cast<u32>(system.GetCpuManager().CurrentCore());
}
}
const auto it = host_thread_ids.find(this_id); const auto it = host_thread_ids.find(this_id);
if (it == host_thread_ids.end()) { if (it == host_thread_ids.end()) {
return Core::INVALID_HOST_THREAD_ID; return Core::INVALID_HOST_THREAD_ID;
@ -240,7 +230,7 @@ struct KernelCore::Impl {
} }
const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler(); const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler();
const Kernel::Thread* current = sched.GetCurrentThread(); const Kernel::Thread* current = sched.GetCurrentThread();
if (current != nullptr) { if (current != nullptr && !current->IsPhantomMode()) {
result.guest_handle = current->GetGlobalHandle(); result.guest_handle = current->GetGlobalHandle();
} else { } else {
result.guest_handle = InvalidHandle; result.guest_handle = InvalidHandle;
@ -313,7 +303,6 @@ struct KernelCore::Impl {
std::shared_ptr<ResourceLimit> system_resource_limit; std::shared_ptr<ResourceLimit> system_resource_limit;
std::shared_ptr<Core::Timing::EventType> thread_wakeup_event_type;
std::shared_ptr<Core::Timing::EventType> preemption_event; std::shared_ptr<Core::Timing::EventType> preemption_event;
// This is the kernel's handle table or supervisor handle table which // This is the kernel's handle table or supervisor handle table which
@ -343,6 +332,15 @@ struct KernelCore::Impl {
std::shared_ptr<Kernel::SharedMemory> irs_shared_mem; std::shared_ptr<Kernel::SharedMemory> irs_shared_mem;
std::shared_ptr<Kernel::SharedMemory> time_shared_mem; std::shared_ptr<Kernel::SharedMemory> time_shared_mem;
std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
std::array<std::unique_ptr<Kernel::Scheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
bool is_multicore{};
std::thread::id single_core_thread_id{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{};
// System context // System context
Core::System& system; Core::System& system;
}; };
@ -352,6 +350,10 @@ KernelCore::~KernelCore() {
Shutdown(); Shutdown();
} }
void KernelCore::SetMulticore(bool is_multicore) {
impl->SetMulticore(is_multicore);
}
void KernelCore::Initialize() { void KernelCore::Initialize() {
impl->Initialize(*this); impl->Initialize(*this);
} }
@ -397,11 +399,11 @@ const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
} }
Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) { Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) {
return impl->cores[id].Scheduler(); return *impl->schedulers[id];
} }
const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const { const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const {
return impl->cores[id].Scheduler(); return *impl->schedulers[id];
} }
Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) { Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
@ -412,6 +414,39 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
return impl->cores[id]; return impl->cores[id];
} }
Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
u32 core_id = impl->GetCurrentHostThreadID();
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
return impl->cores[core_id];
}
const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
u32 core_id = impl->GetCurrentHostThreadID();
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
return impl->cores[core_id];
}
Kernel::Scheduler& KernelCore::CurrentScheduler() {
u32 core_id = impl->GetCurrentHostThreadID();
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
return *impl->schedulers[core_id];
}
const Kernel::Scheduler& KernelCore::CurrentScheduler() const {
u32 core_id = impl->GetCurrentHostThreadID();
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
return *impl->schedulers[core_id];
}
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
return impl->interrupts;
}
const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts()
const {
return impl->interrupts;
}
Kernel::Synchronization& KernelCore::Synchronization() { Kernel::Synchronization& KernelCore::Synchronization() {
return impl->synchronization; return impl->synchronization;
} }
@ -437,15 +472,17 @@ const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
} }
void KernelCore::InvalidateAllInstructionCaches() { void KernelCore::InvalidateAllInstructionCaches() {
for (std::size_t i = 0; i < impl->global_scheduler.CpuCoresCount(); i++) { auto& threads = GlobalScheduler().GetThreadList();
PhysicalCore(i).ArmInterface().ClearInstructionCache(); for (auto& thread : threads) {
if (!thread->IsHLEThread()) {
auto& arm_interface = thread->ArmInterface();
arm_interface.ClearInstructionCache();
}
} }
} }
void KernelCore::PrepareReschedule(std::size_t id) { void KernelCore::PrepareReschedule(std::size_t id) {
if (id < impl->global_scheduler.CpuCoresCount()) { // TODO: Reimplement, this
impl->cores[id].Stop();
}
} }
void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) { void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
@ -481,10 +518,6 @@ u64 KernelCore::CreateNewUserProcessID() {
return impl->next_user_process_id++; return impl->next_user_process_id++;
} }
const std::shared_ptr<Core::Timing::EventType>& KernelCore::ThreadWakeupCallbackEventType() const {
return impl->thread_wakeup_event_type;
}
Kernel::HandleTable& KernelCore::GlobalHandleTable() { Kernel::HandleTable& KernelCore::GlobalHandleTable() {
return impl->global_handle_table; return impl->global_handle_table;
} }
@ -557,4 +590,34 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const {
return *impl->time_shared_mem; return *impl->time_shared_mem;
} }
void KernelCore::Suspend(bool in_suspention) {
const bool should_suspend = exception_exited || in_suspention;
{
SchedulerLock lock(*this);
ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep;
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
impl->suspend_threads[i]->SetStatus(status);
}
}
}
bool KernelCore::IsMulticore() const {
return impl->is_multicore;
}
void KernelCore::ExceptionalExit() {
exception_exited = true;
Suspend(true);
}
void KernelCore::EnterSVCProfile() {
std::size_t core = impl->GetCurrentHostThreadID();
impl->svc_ticks[core] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC));
}
void KernelCore::ExitSVCProfile() {
std::size_t core = impl->GetCurrentHostThreadID();
MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]);
}
} // namespace Kernel } // namespace Kernel

@ -4,15 +4,17 @@
#pragma once #pragma once
#include <array>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "core/hardware_properties.h"
#include "core/hle/kernel/memory/memory_types.h" #include "core/hle/kernel/memory/memory_types.h"
#include "core/hle/kernel/object.h" #include "core/hle/kernel/object.h"
namespace Core { namespace Core {
struct EmuThreadHandle; class CPUInterruptHandler;
class ExclusiveMonitor; class ExclusiveMonitor;
class System; class System;
} // namespace Core } // namespace Core
@ -65,6 +67,9 @@ public:
KernelCore(KernelCore&&) = delete; KernelCore(KernelCore&&) = delete;
KernelCore& operator=(KernelCore&&) = delete; KernelCore& operator=(KernelCore&&) = delete;
/// Sets if emulation is multicore or single core, must be set before Initialize
void SetMulticore(bool is_multicore);
/// Resets the kernel to a clean slate for use. /// Resets the kernel to a clean slate for use.
void Initialize(); void Initialize();
@ -110,6 +115,18 @@ 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 sole instance of the Scheduler at the current running core.
Kernel::Scheduler& CurrentScheduler();
/// Gets the sole instance of the Scheduler at the current running core.
const Kernel::Scheduler& CurrentScheduler() const;
/// Gets the an instance of the current physical CPU core.
Kernel::PhysicalCore& CurrentPhysicalCore();
/// Gets the an instance of the current physical CPU core.
const Kernel::PhysicalCore& CurrentPhysicalCore() const;
/// Gets the an instance of the Synchronization Interface. /// Gets the an instance of the Synchronization Interface.
Kernel::Synchronization& Synchronization(); Kernel::Synchronization& Synchronization();
@ -129,6 +146,10 @@ public:
const Core::ExclusiveMonitor& GetExclusiveMonitor() const; const Core::ExclusiveMonitor& GetExclusiveMonitor() const;
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts();
const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts() const;
void InvalidateAllInstructionCaches(); void InvalidateAllInstructionCaches();
/// Adds a port to the named port table /// Adds a port to the named port table
@ -191,6 +212,18 @@ public:
/// Gets the shared memory object for Time services. /// Gets the shared memory object for Time services.
const Kernel::SharedMemory& GetTimeSharedMem() const; const Kernel::SharedMemory& GetTimeSharedMem() const;
/// Suspend/unsuspend the OS.
void Suspend(bool in_suspention);
/// Exceptional exit the OS.
void ExceptionalExit();
bool IsMulticore() const;
void EnterSVCProfile();
void ExitSVCProfile();
private: private:
friend class Object; friend class Object;
friend class Process; friend class Process;
@ -208,9 +241,6 @@ private:
/// Creates a new thread ID, incrementing the internal thread ID counter. /// Creates a new thread ID, incrementing the internal thread ID counter.
u64 CreateNewThreadID(); u64 CreateNewThreadID();
/// Retrieves the event type used for thread wakeup callbacks.
const std::shared_ptr<Core::Timing::EventType>& ThreadWakeupCallbackEventType() const;
/// Provides a reference to the global handle table. /// Provides a reference to the global handle table.
Kernel::HandleTable& GlobalHandleTable(); Kernel::HandleTable& GlobalHandleTable();
@ -219,6 +249,7 @@ private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl; std::unique_ptr<Impl> impl;
bool exception_exited{};
}; };
} // namespace Kernel } // namespace Kernel

@ -139,7 +139,6 @@ ResultCode MemoryManager::Allocate(PageLinkedList& page_list, std::size_t num_pa
} }
// Only succeed if we allocated as many pages as we wanted // Only succeed if we allocated as many pages as we wanted
ASSERT(num_pages >= 0);
if (num_pages) { if (num_pages) {
return ERR_OUT_OF_MEMORY; return ERR_OUT_OF_MEMORY;
} }

@ -34,8 +34,6 @@ static std::pair<std::shared_ptr<Thread>, u32> GetHighestPriorityMutexWaitingThr
if (thread->GetMutexWaitAddress() != mutex_addr) if (thread->GetMutexWaitAddress() != mutex_addr)
continue; continue;
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
++num_waiters; ++num_waiters;
if (highest_priority_thread == nullptr || if (highest_priority_thread == nullptr ||
thread->GetPriority() < highest_priority_thread->GetPriority()) { thread->GetPriority() < highest_priority_thread->GetPriority()) {
@ -49,6 +47,7 @@ static std::pair<std::shared_ptr<Thread>, u32> GetHighestPriorityMutexWaitingThr
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner. /// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
static void TransferMutexOwnership(VAddr mutex_addr, std::shared_ptr<Thread> current_thread, static void TransferMutexOwnership(VAddr mutex_addr, std::shared_ptr<Thread> current_thread,
std::shared_ptr<Thread> new_owner) { std::shared_ptr<Thread> new_owner) {
current_thread->RemoveMutexWaiter(new_owner);
const auto threads = current_thread->GetMutexWaitingThreads(); const auto threads = current_thread->GetMutexWaitingThreads();
for (const auto& thread : threads) { for (const auto& thread : threads) {
if (thread->GetMutexWaitAddress() != mutex_addr) if (thread->GetMutexWaitAddress() != mutex_addr)
@ -72,85 +71,100 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
return ERR_INVALID_ADDRESS; return ERR_INVALID_ADDRESS;
} }
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); auto& kernel = system.Kernel();
std::shared_ptr<Thread> current_thread = std::shared_ptr<Thread> current_thread =
SharedFrom(system.CurrentScheduler().GetCurrentThread()); SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
std::shared_ptr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle); {
std::shared_ptr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle); SchedulerLock lock(kernel);
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
return ERR_INVALID_ADDRESS;
}
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
// thread. std::shared_ptr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
ASSERT(requesting_thread == current_thread); std::shared_ptr<Thread> requesting_thread =
handle_table.Get<Thread>(requesting_thread_handle);
const u32 addr_value = system.Memory().Read32(address); // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of
// another thread.
ASSERT(requesting_thread == current_thread);
// If the mutex isn't being held, just return success. current_thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
return RESULT_SUCCESS; const u32 addr_value = system.Memory().Read32(address);
// If the mutex isn't being held, just return success.
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
return RESULT_SUCCESS;
}
if (holding_thread == nullptr) {
return ERR_INVALID_HANDLE;
}
// Wait until the mutex is released
current_thread->SetMutexWaitAddress(address);
current_thread->SetWaitHandle(requesting_thread_handle);
current_thread->SetStatus(ThreadStatus::WaitMutex);
// Update the lock holder thread's priority to prevent priority inversion.
holding_thread->AddMutexWaiter(current_thread);
} }
if (holding_thread == nullptr) { {
LOG_ERROR(Kernel, "Holding thread does not exist! thread_handle={:08X}", SchedulerLock lock(kernel);
holding_thread_handle); auto* owner = current_thread->GetLockOwner();
return ERR_INVALID_HANDLE; if (owner != nullptr) {
owner->RemoveMutexWaiter(current_thread);
}
} }
return current_thread->GetSignalingResult();
// Wait until the mutex is released
current_thread->SetMutexWaitAddress(address);
current_thread->SetWaitHandle(requesting_thread_handle);
current_thread->SetStatus(ThreadStatus::WaitMutex);
current_thread->InvalidateWakeupCallback();
// Update the lock holder thread's priority to prevent priority inversion.
holding_thread->AddMutexWaiter(current_thread);
system.PrepareReschedule();
return RESULT_SUCCESS;
} }
ResultCode Mutex::Release(VAddr address) { std::pair<ResultCode, std::shared_ptr<Thread>> Mutex::Unlock(std::shared_ptr<Thread> owner,
VAddr address) {
// The mutex address must be 4-byte aligned // The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) { if ((address % sizeof(u32)) != 0) {
LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address); LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address);
return ERR_INVALID_ADDRESS; return {ERR_INVALID_ADDRESS, nullptr};
} }
std::shared_ptr<Thread> current_thread = auto [new_owner, num_waiters] = GetHighestPriorityMutexWaitingThread(owner, address);
SharedFrom(system.CurrentScheduler().GetCurrentThread()); if (new_owner == nullptr) {
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
// There are no more threads waiting for the mutex, release it completely.
if (thread == nullptr) {
system.Memory().Write32(address, 0); system.Memory().Write32(address, 0);
return RESULT_SUCCESS; return {RESULT_SUCCESS, nullptr};
} }
// Transfer the ownership of the mutex from the previous owner to the new one. // Transfer the ownership of the mutex from the previous owner to the new one.
TransferMutexOwnership(address, current_thread, thread); TransferMutexOwnership(address, owner, new_owner);
u32 mutex_value = new_owner->GetWaitHandle();
u32 mutex_value = thread->GetWaitHandle();
if (num_waiters >= 2) { if (num_waiters >= 2) {
// Notify the guest that there are still some threads waiting for the mutex // Notify the guest that there are still some threads waiting for the mutex
mutex_value |= Mutex::MutexHasWaitersFlag; mutex_value |= Mutex::MutexHasWaitersFlag;
} }
new_owner->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
new_owner->SetLockOwner(nullptr);
new_owner->ResumeFromWait();
// Grant the mutex to the next waiting thread and resume it.
system.Memory().Write32(address, mutex_value); system.Memory().Write32(address, mutex_value);
return {RESULT_SUCCESS, new_owner};
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
thread->ResumeFromWait();
thread->SetLockOwner(nullptr);
thread->SetCondVarWaitAddress(0);
thread->SetMutexWaitAddress(0);
thread->SetWaitHandle(0);
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
system.PrepareReschedule();
return RESULT_SUCCESS;
} }
ResultCode Mutex::Release(VAddr address) {
auto& kernel = system.Kernel();
SchedulerLock lock(kernel);
std::shared_ptr<Thread> current_thread =
SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
auto [result, new_owner] = Unlock(current_thread, address);
if (result != RESULT_SUCCESS && new_owner != nullptr) {
new_owner->SetSynchronizationResults(nullptr, result);
}
return result;
}
} // namespace Kernel } // namespace Kernel

@ -28,6 +28,10 @@ public:
ResultCode TryAcquire(VAddr address, Handle holding_thread_handle, ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
Handle requesting_thread_handle); Handle requesting_thread_handle);
/// Unlocks a mutex for owner at address
std::pair<ResultCode, std::shared_ptr<Thread>> Unlock(std::shared_ptr<Thread> owner,
VAddr address);
/// Releases the mutex at the specified address. /// Releases the mutex at the specified address.
ResultCode Release(VAddr address); ResultCode Release(VAddr address);

@ -2,12 +2,15 @@
// 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 "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/spin_lock.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h" #include "core/arm/dynarmic/arm_dynarmic_64.h"
#endif #endif
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h" #include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h" #include "core/core.h"
@ -17,50 +20,37 @@
namespace Kernel { namespace Kernel {
PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler,
Core::ExclusiveMonitor& exclusive_monitor) Core::CPUInterruptHandler& interrupt_handler)
: core_index{id} { : interrupt_handler{interrupt_handler}, core_index{id}, scheduler{scheduler} {
#ifdef ARCHITECTURE_x86_64
arm_interface_32 =
std::make_unique<Core::ARM_Dynarmic_32>(system, exclusive_monitor, core_index);
arm_interface_64 =
std::make_unique<Core::ARM_Dynarmic_64>(system, exclusive_monitor, core_index);
#else guard = std::make_unique<Common::SpinLock>();
using Core::ARM_Unicorn;
arm_interface_32 = std::make_unique<ARM_Unicorn>(system, ARM_Unicorn::Arch::AArch32);
arm_interface_64 = std::make_unique<ARM_Unicorn>(system, ARM_Unicorn::Arch::AArch64);
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
scheduler = std::make_unique<Kernel::Scheduler>(system, core_index);
} }
PhysicalCore::~PhysicalCore() = default; PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Run() { void PhysicalCore::Idle() {
arm_interface->Run(); interrupt_handler.AwaitInterrupt();
arm_interface->ClearExclusiveState();
}
void PhysicalCore::Step() {
arm_interface->Step();
}
void PhysicalCore::Stop() {
arm_interface->PrepareReschedule();
} }
void PhysicalCore::Shutdown() { void PhysicalCore::Shutdown() {
scheduler->Shutdown(); scheduler.Shutdown();
} }
void PhysicalCore::SetIs64Bit(bool is_64_bit) { bool PhysicalCore::IsInterrupted() const {
if (is_64_bit) { return interrupt_handler.IsInterrupted();
arm_interface = arm_interface_64.get(); }
} else {
arm_interface = arm_interface_32.get(); void PhysicalCore::Interrupt() {
} guard->lock();
interrupt_handler.SetInterrupt(true);
guard->unlock();
}
void PhysicalCore::ClearInterrupt() {
guard->lock();
interrupt_handler.SetInterrupt(false);
guard->unlock();
} }
} // namespace Kernel } // namespace Kernel

@ -7,12 +7,17 @@
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
namespace Common {
class SpinLock;
}
namespace Kernel { namespace Kernel {
class Scheduler; class Scheduler;
} // namespace Kernel } // namespace Kernel
namespace Core { namespace Core {
class ARM_Interface; class ARM_Interface;
class CPUInterruptHandler;
class ExclusiveMonitor; class ExclusiveMonitor;
class System; class System;
} // namespace Core } // namespace Core
@ -21,7 +26,8 @@ namespace Kernel {
class PhysicalCore { class PhysicalCore {
public: public:
PhysicalCore(Core::System& system, std::size_t id, Core::ExclusiveMonitor& exclusive_monitor); PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler,
Core::CPUInterruptHandler& interrupt_handler);
~PhysicalCore(); ~PhysicalCore();
PhysicalCore(const PhysicalCore&) = delete; PhysicalCore(const PhysicalCore&) = delete;
@ -30,24 +36,19 @@ public:
PhysicalCore(PhysicalCore&&) = default; PhysicalCore(PhysicalCore&&) = default;
PhysicalCore& operator=(PhysicalCore&&) = default; PhysicalCore& operator=(PhysicalCore&&) = default;
/// Execute current jit state void Idle();
void Run(); /// Interrupt this physical core.
/// Execute a single instruction in current jit. void Interrupt();
void Step();
/// Stop JIT execution/exit /// Clear this core's interrupt
void Stop(); void ClearInterrupt();
/// Check if this core is interrupted
bool IsInterrupted() const;
// Shutdown this physical core. // Shutdown this physical core.
void Shutdown(); void Shutdown();
Core::ARM_Interface& ArmInterface() {
return *arm_interface;
}
const Core::ARM_Interface& ArmInterface() const {
return *arm_interface;
}
bool IsMainCore() const { bool IsMainCore() const {
return core_index == 0; return core_index == 0;
} }
@ -61,21 +62,18 @@ public:
} }
Kernel::Scheduler& Scheduler() { Kernel::Scheduler& Scheduler() {
return *scheduler; return scheduler;
} }
const Kernel::Scheduler& Scheduler() const { const Kernel::Scheduler& Scheduler() const {
return *scheduler; return scheduler;
} }
void SetIs64Bit(bool is_64_bit);
private: private:
Core::CPUInterruptHandler& interrupt_handler;
std::size_t core_index; std::size_t core_index;
std::unique_ptr<Core::ARM_Interface> arm_interface_32; Kernel::Scheduler& scheduler;
std::unique_ptr<Core::ARM_Interface> arm_interface_64; std::unique_ptr<Common::SpinLock> guard;
std::unique_ptr<Kernel::Scheduler> scheduler;
Core::ARM_Interface* arm_interface{};
}; };
} // namespace Kernel } // namespace Kernel

@ -22,6 +22,7 @@
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/settings.h" #include "core/settings.h"
@ -30,14 +31,15 @@ namespace {
/** /**
* Sets up the primary application thread * Sets up the primary application thread
* *
* @param system The system instance to create the main thread under.
* @param owner_process The parent process for the main thread * @param owner_process The parent process for the main thread
* @param kernel The kernel instance to create the main thread under.
* @param priority The priority to give the main thread * @param priority The priority to give the main thread
*/ */
void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority, VAddr stack_top) { void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, VAddr stack_top) {
const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart(); const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart();
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, ThreadType type = THREADTYPE_USER;
owner_process.GetIdealCore(), stack_top, owner_process); auto thread_res = Thread::Create(system, type, "main", entry_point, priority, 0,
owner_process.GetIdealCore(), stack_top, &owner_process);
std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap(); std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
@ -48,8 +50,12 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority, V
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;
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
thread->ResumeFromWait(); {
SchedulerLock lock{kernel};
thread->SetStatus(ThreadStatus::Ready);
}
} }
} // Anonymous namespace } // Anonymous namespace
@ -182,7 +188,6 @@ void Process::RemoveConditionVariableThread(std::shared_ptr<Thread> thread) {
} }
++it; ++it;
} }
UNREACHABLE();
} }
std::vector<std::shared_ptr<Thread>> Process::GetConditionVariableThreads( std::vector<std::shared_ptr<Thread>> Process::GetConditionVariableThreads(
@ -207,6 +212,7 @@ void Process::UnregisterThread(const Thread* thread) {
} }
ResultCode Process::ClearSignalState() { ResultCode Process::ClearSignalState() {
SchedulerLock lock(system.Kernel());
if (status == ProcessStatus::Exited) { if (status == ProcessStatus::Exited) {
LOG_ERROR(Kernel, "called on a terminated process instance."); LOG_ERROR(Kernel, "called on a terminated process instance.");
return ERR_INVALID_STATE; return ERR_INVALID_STATE;
@ -294,7 +300,7 @@ void Process::Run(s32 main_thread_priority, u64 stack_size) {
ChangeStatus(ProcessStatus::Running); ChangeStatus(ProcessStatus::Running);
SetupMainThread(*this, kernel, main_thread_priority, main_thread_stack_top); SetupMainThread(system, *this, main_thread_priority, main_thread_stack_top);
resource_limit->Reserve(ResourceType::Threads, 1); resource_limit->Reserve(ResourceType::Threads, 1);
resource_limit->Reserve(ResourceType::PhysicalMemory, main_thread_stack_size); resource_limit->Reserve(ResourceType::PhysicalMemory, main_thread_stack_size);
} }
@ -340,6 +346,7 @@ static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
} }
VAddr Process::CreateTLSRegion() { VAddr Process::CreateTLSRegion() {
SchedulerLock lock(system.Kernel());
if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)}; if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)};
tls_page_iter != tls_pages.cend()) { tls_page_iter != tls_pages.cend()) {
return *tls_page_iter->ReserveSlot(); return *tls_page_iter->ReserveSlot();
@ -370,6 +377,7 @@ VAddr Process::CreateTLSRegion() {
} }
void Process::FreeTLSRegion(VAddr tls_address) { void Process::FreeTLSRegion(VAddr tls_address) {
SchedulerLock lock(system.Kernel());
const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE); const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE);
auto iter = auto iter =
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) { std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
@ -384,6 +392,7 @@ void Process::FreeTLSRegion(VAddr tls_address) {
} }
void Process::LoadModule(CodeSet code_set, VAddr base_addr) { void Process::LoadModule(CodeSet code_set, VAddr base_addr) {
std::lock_guard lock{HLE::g_hle_lock};
const auto ReprotectSegment = [&](const CodeSet::Segment& segment, const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
Memory::MemoryPermission permission) { Memory::MemoryPermission permission) {
page_table->SetCodeMemoryPermission(segment.addr + base_addr, segment.size, permission); page_table->SetCodeMemoryPermission(segment.addr + base_addr, segment.size, permission);

@ -6,8 +6,10 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/hle/kernel/errors.h" #include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h" #include "core/hle/kernel/object.h"
#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
namespace Kernel { namespace Kernel {
@ -37,6 +39,7 @@ void ReadableEvent::Clear() {
} }
ResultCode ReadableEvent::Reset() { ResultCode ReadableEvent::Reset() {
SchedulerLock lock(kernel);
if (!is_signaled) { if (!is_signaled) {
LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}", LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}",
GetObjectId(), GetTypeName(), GetName()); GetObjectId(), GetTypeName(), GetName());

@ -11,11 +11,15 @@
#include <utility> #include <utility>
#include "common/assert.h" #include "common/assert.h"
#include "common/bit_util.h"
#include "common/fiber.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/time_manager.h" #include "core/hle/kernel/time_manager.h"
@ -27,103 +31,151 @@ GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {}
GlobalScheduler::~GlobalScheduler() = default; GlobalScheduler::~GlobalScheduler() = default;
void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) { void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) {
global_list_guard.lock();
thread_list.push_back(std::move(thread)); thread_list.push_back(std::move(thread));
global_list_guard.unlock();
} }
void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) { void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) {
global_list_guard.lock();
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
thread_list.end()); thread_list.end());
global_list_guard.unlock();
} }
void GlobalScheduler::UnloadThread(std::size_t core) { u32 GlobalScheduler::SelectThreads() {
Scheduler& sched = kernel.Scheduler(core); ASSERT(is_locked);
sched.UnloadThread();
}
void GlobalScheduler::SelectThread(std::size_t core) {
const auto update_thread = [](Thread* thread, Scheduler& sched) { const auto update_thread = [](Thread* thread, Scheduler& sched) {
if (thread != sched.selected_thread.get()) { sched.guard.lock();
if (thread != sched.selected_thread_set.get()) {
if (thread == nullptr) { if (thread == nullptr) {
++sched.idle_selection_count; ++sched.idle_selection_count;
} }
sched.selected_thread = SharedFrom(thread); sched.selected_thread_set = SharedFrom(thread);
} }
sched.is_context_switch_pending = sched.selected_thread != sched.current_thread; const bool reschedule_pending =
sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread);
sched.is_context_switch_pending = reschedule_pending;
std::atomic_thread_fence(std::memory_order_seq_cst); std::atomic_thread_fence(std::memory_order_seq_cst);
sched.guard.unlock();
return reschedule_pending;
}; };
Scheduler& sched = kernel.Scheduler(core); if (!is_reselection_pending.load()) {
Thread* current_thread = nullptr; return 0;
}
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> top_threads{};
u32 idle_cores{};
// Step 1: Get top thread in schedule queue. // Step 1: Get top thread in schedule queue.
current_thread = scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front(); for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (current_thread) { Thread* top_thread =
update_thread(current_thread, sched); scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
return; if (top_thread != nullptr) {
} // TODO(Blinkhawk): Implement Thread Pinning
// Step 2: Try selecting a suggested thread. } else {
Thread* winner = nullptr; idle_cores |= (1ul << core);
std::set<s32> sug_cores;
for (auto thread : suggested_queue[core]) {
s32 this_core = thread->GetProcessorID();
Thread* thread_on_core = nullptr;
if (this_core >= 0) {
thread_on_core = scheduled_queue[this_core].front();
} }
if (this_core < 0 || thread != thread_on_core) { top_threads[core] = top_thread;
winner = thread;
break;
}
sug_cores.insert(this_core);
} }
// if we got a suggested thread, select it, else do a second pass.
if (winner && winner->GetPriority() > 2) { while (idle_cores != 0) {
if (winner->IsRunning()) { u32 core_id = Common::CountTrailingZeroes32(idle_cores);
UnloadThread(static_cast<u32>(winner->GetProcessorID()));
} if (!suggested_queue[core_id].empty()) {
TransferToCore(winner->GetPriority(), static_cast<s32>(core), winner); std::array<s32, Core::Hardware::NUM_CPU_CORES> migration_candidates{};
update_thread(winner, sched); std::size_t num_candidates = 0;
return; auto iter = suggested_queue[core_id].begin();
} Thread* suggested = nullptr;
// Step 3: Select a suggested thread from another core // Step 2: Try selecting a suggested thread.
for (auto& src_core : sug_cores) { while (iter != suggested_queue[core_id].end()) {
auto it = scheduled_queue[src_core].begin(); suggested = *iter;
it++; iter++;
if (it != scheduled_queue[src_core].end()) { s32 suggested_core_id = suggested->GetProcessorID();
Thread* thread_on_core = scheduled_queue[src_core].front(); Thread* top_thread =
Thread* to_change = *it; suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr;
if (thread_on_core->IsRunning() || to_change->IsRunning()) { if (top_thread != suggested) {
UnloadThread(static_cast<u32>(src_core)); if (top_thread != nullptr &&
top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) {
suggested = nullptr;
break;
// There's a too high thread to do core migration, cancel
}
TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), suggested);
break;
}
suggested = nullptr;
migration_candidates[num_candidates++] = suggested_core_id;
} }
TransferToCore(thread_on_core->GetPriority(), static_cast<s32>(core), thread_on_core); // Step 3: Select a suggested thread from another core
current_thread = thread_on_core; if (suggested == nullptr) {
break; for (std::size_t i = 0; i < num_candidates; i++) {
s32 candidate_core = migration_candidates[i];
suggested = top_threads[candidate_core];
auto it = scheduled_queue[candidate_core].begin();
it++;
Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr;
if (next != nullptr) {
TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id),
suggested);
top_threads[candidate_core] = next;
break;
} else {
suggested = nullptr;
}
}
}
top_threads[core_id] = suggested;
}
idle_cores &= ~(1ul << core_id);
}
u32 cores_needing_context_switch{};
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
Scheduler& sched = kernel.Scheduler(core);
ASSERT(top_threads[core] == nullptr || top_threads[core]->GetProcessorID() == core);
if (update_thread(top_threads[core], sched)) {
cores_needing_context_switch |= (1ul << core);
} }
} }
update_thread(current_thread, sched); return cores_needing_context_switch;
} }
bool GlobalScheduler::YieldThread(Thread* yielding_thread) { bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
ASSERT(is_locked);
// Note: caller should use critical section, etc. // Note: caller should use critical section, etc.
if (!yielding_thread->IsRunnable()) {
// Normally this case shouldn't happen except for SetThreadActivity.
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID()); const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
const u32 priority = yielding_thread->GetPriority(); const u32 priority = yielding_thread->GetPriority();
// Yield the thread // Yield the thread
const Thread* const winner = scheduled_queue[core_id].front(priority); Reschedule(priority, core_id, yielding_thread);
ASSERT_MSG(yielding_thread == winner, "Thread yielding without being in front"); const Thread* const winner = scheduled_queue[core_id].front();
scheduled_queue[core_id].yield(priority); if (kernel.GetCurrentHostThreadID() != core_id) {
is_reselection_pending.store(true, std::memory_order_release);
}
return AskForReselectionOrMarkRedundant(yielding_thread, winner); return AskForReselectionOrMarkRedundant(yielding_thread, winner);
} }
bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) { bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
ASSERT(is_locked);
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section, // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
// etc. // etc.
if (!yielding_thread->IsRunnable()) {
// Normally this case shouldn't happen except for SetThreadActivity.
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID()); const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
const u32 priority = yielding_thread->GetPriority(); const u32 priority = yielding_thread->GetPriority();
// Yield the thread // Yield the thread
ASSERT_MSG(yielding_thread == scheduled_queue[core_id].front(priority), Reschedule(priority, core_id, yielding_thread);
"Thread yielding without being in front");
scheduled_queue[core_id].yield(priority);
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads; std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
for (std::size_t i = 0; i < current_threads.size(); i++) { for (std::size_t i = 0; i < current_threads.size(); i++) {
@ -153,21 +205,28 @@ bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
if (winner != nullptr) { if (winner != nullptr) {
if (winner != yielding_thread) { if (winner != yielding_thread) {
if (winner->IsRunning()) {
UnloadThread(static_cast<u32>(winner->GetProcessorID()));
}
TransferToCore(winner->GetPriority(), s32(core_id), winner); TransferToCore(winner->GetPriority(), s32(core_id), winner);
} }
} else { } else {
winner = next_thread; winner = next_thread;
} }
if (kernel.GetCurrentHostThreadID() != core_id) {
is_reselection_pending.store(true, std::memory_order_release);
}
return AskForReselectionOrMarkRedundant(yielding_thread, winner); return AskForReselectionOrMarkRedundant(yielding_thread, winner);
} }
bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) { bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
ASSERT(is_locked);
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section, // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
// etc. // etc.
if (!yielding_thread->IsRunnable()) {
// Normally this case shouldn't happen except for SetThreadActivity.
is_reselection_pending.store(true, std::memory_order_release);
return false;
}
Thread* winner = nullptr; Thread* winner = nullptr;
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID()); const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
@ -195,25 +254,31 @@ bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread
} }
if (winner != nullptr) { if (winner != nullptr) {
if (winner != yielding_thread) { if (winner != yielding_thread) {
if (winner->IsRunning()) {
UnloadThread(static_cast<u32>(winner->GetProcessorID()));
}
TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner); TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner);
} }
} else { } else {
winner = yielding_thread; winner = yielding_thread;
} }
} else {
winner = scheduled_queue[core_id].front();
}
if (kernel.GetCurrentHostThreadID() != core_id) {
is_reselection_pending.store(true, std::memory_order_release);
} }
return AskForReselectionOrMarkRedundant(yielding_thread, winner); return AskForReselectionOrMarkRedundant(yielding_thread, winner);
} }
void GlobalScheduler::PreemptThreads() { void GlobalScheduler::PreemptThreads() {
ASSERT(is_locked);
for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id]; const u32 priority = preemption_priorities[core_id];
if (scheduled_queue[core_id].size(priority) > 0) { if (scheduled_queue[core_id].size(priority) > 0) {
scheduled_queue[core_id].front(priority)->IncrementYieldCount(); if (scheduled_queue[core_id].size(priority) > 1) {
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
}
scheduled_queue[core_id].yield(priority); scheduled_queue[core_id].yield(priority);
if (scheduled_queue[core_id].size(priority) > 1) { if (scheduled_queue[core_id].size(priority) > 1) {
scheduled_queue[core_id].front(priority)->IncrementYieldCount(); scheduled_queue[core_id].front(priority)->IncrementYieldCount();
@ -247,9 +312,6 @@ void GlobalScheduler::PreemptThreads() {
} }
if (winner != nullptr) { if (winner != nullptr) {
if (winner->IsRunning()) {
UnloadThread(static_cast<u32>(winner->GetProcessorID()));
}
TransferToCore(winner->GetPriority(), s32(core_id), winner); TransferToCore(winner->GetPriority(), s32(core_id), winner);
current_thread = current_thread =
winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread; winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
@ -280,9 +342,6 @@ void GlobalScheduler::PreemptThreads() {
} }
if (winner != nullptr) { if (winner != nullptr) {
if (winner->IsRunning()) {
UnloadThread(static_cast<u32>(winner->GetProcessorID()));
}
TransferToCore(winner->GetPriority(), s32(core_id), winner); TransferToCore(winner->GetPriority(), s32(core_id), winner);
current_thread = winner; current_thread = winner;
} }
@ -292,34 +351,65 @@ void GlobalScheduler::PreemptThreads() {
} }
} }
void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule,
Core::EmuThreadHandle global_thread) {
u32 current_core = global_thread.host_handle;
bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
(current_core < Core::Hardware::NUM_CPU_CORES);
while (cores_pending_reschedule != 0) {
u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule);
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
if (!must_context_switch || core != current_core) {
auto& phys_core = kernel.PhysicalCore(core);
phys_core.Interrupt();
} else {
must_context_switch = true;
}
cores_pending_reschedule &= ~(1ul << core);
}
if (must_context_switch) {
auto& core_scheduler = kernel.CurrentScheduler();
kernel.ExitSVCProfile();
core_scheduler.TryDoContextSwitch();
kernel.EnterSVCProfile();
}
}
void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) { void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
suggested_queue[core].add(thread, priority); suggested_queue[core].add(thread, priority);
} }
void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) { void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
suggested_queue[core].remove(thread, priority); suggested_queue[core].remove(thread, priority);
} }
void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) { void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core."); ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
scheduled_queue[core].add(thread, priority); scheduled_queue[core].add(thread, priority);
} }
void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) { void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core."); ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
scheduled_queue[core].add(thread, priority, false); scheduled_queue[core].add(thread, priority, false);
} }
void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) { void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
scheduled_queue[core].remove(thread, priority); scheduled_queue[core].remove(thread, priority);
scheduled_queue[core].add(thread, priority); scheduled_queue[core].add(thread, priority);
} }
void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) { void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) {
ASSERT(is_locked);
scheduled_queue[core].remove(thread, priority); scheduled_queue[core].remove(thread, priority);
} }
void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) { void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
ASSERT(is_locked);
const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT; const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
const s32 source_core = thread->GetProcessorID(); const s32 source_core = thread->GetProcessorID();
if (source_core == destination_core || !schedulable) { if (source_core == destination_core || !schedulable) {
@ -349,6 +439,108 @@ bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread,
} }
} }
void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) {
if (old_flags == thread->scheduling_state) {
return;
}
ASSERT(is_locked);
if (old_flags == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// In this case the thread was running, now it's pausing/exitting
if (thread->processor_id >= 0) {
Unschedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Unsuggest(thread->current_priority, core, thread);
}
}
} else if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
// The thread is now set to running from being stopped
if (thread->processor_id >= 0) {
Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Suggest(thread->current_priority, core, thread);
}
}
}
SetReselectionPending();
}
void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) {
if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable)) {
return;
}
ASSERT(is_locked);
if (thread->processor_id >= 0) {
Unschedule(old_priority, static_cast<u32>(thread->processor_id), thread);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Unsuggest(old_priority, core, thread);
}
}
if (thread->processor_id >= 0) {
if (thread == kernel.CurrentScheduler().GetCurrentThread()) {
SchedulePrepend(thread->current_priority, static_cast<u32>(thread->processor_id),
thread);
} else {
Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
}
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(thread->processor_id) &&
((thread->affinity_mask >> core) & 1) != 0) {
Suggest(thread->current_priority, core, thread);
}
}
thread->IncrementYieldCount();
SetReselectionPending();
}
void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask,
s32 old_core) {
if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable) ||
thread->current_priority >= THREADPRIO_COUNT) {
return;
}
ASSERT(is_locked);
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (((old_affinity_mask >> core) & 1) != 0) {
if (core == static_cast<u32>(old_core)) {
Unschedule(thread->current_priority, core, thread);
} else {
Unsuggest(thread->current_priority, core, thread);
}
}
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (((thread->affinity_mask >> core) & 1) != 0) {
if (core == static_cast<u32>(thread->processor_id)) {
Schedule(thread->current_priority, core, thread);
} else {
Suggest(thread->current_priority, core, thread);
}
}
}
thread->IncrementYieldCount();
SetReselectionPending();
}
void GlobalScheduler::Shutdown() { void GlobalScheduler::Shutdown() {
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
scheduled_queue[core].clear(); scheduled_queue[core].clear();
@ -359,10 +551,12 @@ void GlobalScheduler::Shutdown() {
void GlobalScheduler::Lock() { void GlobalScheduler::Lock() {
Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID(); Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID();
ASSERT(!current_thread.IsInvalid());
if (current_thread == current_owner) { if (current_thread == current_owner) {
++scope_lock; ++scope_lock;
} else { } else {
inner_lock.lock(); inner_lock.lock();
is_locked = true;
current_owner = current_thread; current_owner = current_thread;
ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle()); ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle());
scope_lock = 1; scope_lock = 1;
@ -374,17 +568,18 @@ void GlobalScheduler::Unlock() {
ASSERT(scope_lock > 0); ASSERT(scope_lock > 0);
return; return;
} }
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { u32 cores_pending_reschedule = SelectThreads();
SelectThread(i); Core::EmuThreadHandle leaving_thread = current_owner;
}
current_owner = Core::EmuThreadHandle::InvalidHandle(); current_owner = Core::EmuThreadHandle::InvalidHandle();
scope_lock = 1; scope_lock = 1;
is_locked = false;
inner_lock.unlock(); inner_lock.unlock();
// TODO(Blinkhawk): Setup the interrupts and change context on current core. EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread);
} }
Scheduler::Scheduler(Core::System& system, std::size_t core_id) Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) {
: system{system}, core_id{core_id} {} switch_fiber = std::make_shared<Common::Fiber>(std::function<void(void*)>(OnSwitch), this);
}
Scheduler::~Scheduler() = default; Scheduler::~Scheduler() = default;
@ -393,56 +588,128 @@ bool Scheduler::HaveReadyThreads() const {
} }
Thread* Scheduler::GetCurrentThread() const { Thread* Scheduler::GetCurrentThread() const {
return current_thread.get(); if (current_thread) {
return current_thread.get();
}
return idle_thread.get();
} }
Thread* Scheduler::GetSelectedThread() const { Thread* Scheduler::GetSelectedThread() const {
return selected_thread.get(); return selected_thread.get();
} }
void Scheduler::SelectThreads() {
system.GlobalScheduler().SelectThread(core_id);
}
u64 Scheduler::GetLastContextSwitchTicks() const { u64 Scheduler::GetLastContextSwitchTicks() const {
return last_context_switch_time; return last_context_switch_time;
} }
void Scheduler::TryDoContextSwitch() { void Scheduler::TryDoContextSwitch() {
auto& phys_core = system.Kernel().CurrentPhysicalCore();
if (phys_core.IsInterrupted()) {
phys_core.ClearInterrupt();
}
guard.lock();
if (is_context_switch_pending) { if (is_context_switch_pending) {
SwitchContext(); SwitchContext();
} else {
guard.unlock();
} }
} }
void Scheduler::UnloadThread() { void Scheduler::OnThreadStart() {
Thread* const previous_thread = GetCurrentThread(); SwitchContextStep2();
Process* const previous_process = system.Kernel().CurrentProcess(); }
UpdateLastContextSwitchTime(previous_thread, previous_process); void Scheduler::Unload() {
Thread* thread = current_thread.get();
// Save context for previous thread if (thread) {
if (previous_thread) { thread->SetContinuousOnSVC(false);
system.ArmInterface(core_id).SaveContext(previous_thread->GetContext32()); thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
system.ArmInterface(core_id).SaveContext(previous_thread->GetContext64()); thread->SetIsRunning(false);
// Save the TPIDR_EL0 system register in case it was modified. if (!thread->IsHLEThread() && !thread->HasExited()) {
previous_thread->SetTPIDR_EL0(system.ArmInterface(core_id).GetTPIDR_EL0()); Core::ARM_Interface& cpu_core = thread->ArmInterface();
cpu_core.SaveContext(thread->GetContext32());
if (previous_thread->GetStatus() == ThreadStatus::Running) { cpu_core.SaveContext(thread->GetContext64());
// This is only the case when a reschedule is triggered without the current thread // Save the TPIDR_EL0 system register in case it was modified.
// yielding execution (i.e. an event triggered, system core time-sliced, etc) thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
previous_thread->SetStatus(ThreadStatus::Ready); cpu_core.ClearExclusiveState();
} }
previous_thread->SetIsRunning(false); thread->context_guard.unlock();
} }
current_thread = nullptr; }
void Scheduler::Reload() {
Thread* thread = current_thread.get();
if (thread) {
ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
"Thread must be runnable.");
// Cancel any outstanding wakeup events for this thread
thread->SetIsRunning(true);
thread->SetWasRunning(false);
thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
auto* const thread_owner_process = thread->GetOwnerProcess();
if (thread_owner_process != nullptr) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
}
if (!thread->IsHLEThread()) {
Core::ARM_Interface& cpu_core = thread->ArmInterface();
cpu_core.LoadContext(thread->GetContext32());
cpu_core.LoadContext(thread->GetContext64());
cpu_core.SetTlsAddress(thread->GetTLSAddress());
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
cpu_core.ChangeProcessorID(this->core_id);
cpu_core.ClearExclusiveState();
}
}
}
void Scheduler::SwitchContextStep2() {
Thread* previous_thread = current_thread_prev.get();
Thread* new_thread = selected_thread.get();
// Load context of new thread
Process* const previous_process =
previous_thread != nullptr ? previous_thread->GetOwnerProcess() : nullptr;
if (new_thread) {
ASSERT_MSG(new_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
"Thread must be runnable.");
// Cancel any outstanding wakeup events for this thread
new_thread->SetIsRunning(true);
new_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
new_thread->SetWasRunning(false);
auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (thread_owner_process != nullptr) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
}
if (!new_thread->IsHLEThread()) {
Core::ARM_Interface& cpu_core = new_thread->ArmInterface();
cpu_core.LoadContext(new_thread->GetContext32());
cpu_core.LoadContext(new_thread->GetContext64());
cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
cpu_core.ChangeProcessorID(this->core_id);
cpu_core.ClearExclusiveState();
}
}
TryDoContextSwitch();
} }
void Scheduler::SwitchContext() { void Scheduler::SwitchContext() {
Thread* const previous_thread = GetCurrentThread(); current_thread_prev = current_thread;
Thread* const new_thread = GetSelectedThread(); selected_thread = selected_thread_set;
Thread* previous_thread = current_thread_prev.get();
Thread* new_thread = selected_thread.get();
current_thread = selected_thread;
is_context_switch_pending = false; is_context_switch_pending = false;
if (new_thread == previous_thread) { if (new_thread == previous_thread) {
guard.unlock();
return; return;
} }
@ -452,51 +719,75 @@ void Scheduler::SwitchContext() {
// Save context for previous thread // Save context for previous thread
if (previous_thread) { if (previous_thread) {
system.ArmInterface(core_id).SaveContext(previous_thread->GetContext32()); if (new_thread != nullptr && new_thread->IsSuspendThread()) {
system.ArmInterface(core_id).SaveContext(previous_thread->GetContext64()); previous_thread->SetWasRunning(true);
// Save the TPIDR_EL0 system register in case it was modified.
previous_thread->SetTPIDR_EL0(system.ArmInterface(core_id).GetTPIDR_EL0());
if (previous_thread->GetStatus() == ThreadStatus::Running) {
// This is only the case when a reschedule is triggered without the current thread
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
previous_thread->SetStatus(ThreadStatus::Ready);
} }
previous_thread->SetContinuousOnSVC(false);
previous_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
previous_thread->SetIsRunning(false); previous_thread->SetIsRunning(false);
if (!previous_thread->IsHLEThread() && !previous_thread->HasExited()) {
Core::ARM_Interface& cpu_core = previous_thread->ArmInterface();
cpu_core.SaveContext(previous_thread->GetContext32());
cpu_core.SaveContext(previous_thread->GetContext64());
// Save the TPIDR_EL0 system register in case it was modified.
previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
cpu_core.ClearExclusiveState();
}
previous_thread->context_guard.unlock();
} }
// Load context of new thread std::shared_ptr<Common::Fiber>* old_context;
if (new_thread) { if (previous_thread != nullptr) {
ASSERT_MSG(new_thread->GetProcessorID() == s32(this->core_id), old_context = &previous_thread->GetHostContext();
"Thread must be assigned to this core.");
ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
"Thread must be ready to become running.");
// Cancel any outstanding wakeup events for this thread
new_thread->CancelWakeupTimer();
current_thread = SharedFrom(new_thread);
new_thread->SetStatus(ThreadStatus::Running);
new_thread->SetIsRunning(true);
auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (previous_process != thread_owner_process) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
}
system.ArmInterface(core_id).LoadContext(new_thread->GetContext32());
system.ArmInterface(core_id).LoadContext(new_thread->GetContext64());
system.ArmInterface(core_id).SetTlsAddress(new_thread->GetTLSAddress());
system.ArmInterface(core_id).SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
} else { } else {
current_thread = nullptr; old_context = &idle_thread->GetHostContext();
// Note: We do not reset the current process and current page table when idling because }
// technically we haven't changed processes, our threads are just paused. guard.unlock();
Common::Fiber::YieldTo(*old_context, switch_fiber);
/// When a thread wakes up, the scheduler may have changed to other in another core.
auto& next_scheduler = system.Kernel().CurrentScheduler();
next_scheduler.SwitchContextStep2();
}
void Scheduler::OnSwitch(void* this_scheduler) {
Scheduler* sched = static_cast<Scheduler*>(this_scheduler);
sched->SwitchToCurrent();
}
void Scheduler::SwitchToCurrent() {
while (true) {
guard.lock();
selected_thread = selected_thread_set;
current_thread = selected_thread;
is_context_switch_pending = false;
guard.unlock();
while (!is_context_switch_pending) {
if (current_thread != nullptr && !current_thread->IsHLEThread()) {
current_thread->context_guard.lock();
if (!current_thread->IsRunnable()) {
current_thread->context_guard.unlock();
break;
}
if (current_thread->GetProcessorID() != core_id) {
current_thread->context_guard.unlock();
break;
}
}
std::shared_ptr<Common::Fiber>* next_context;
if (current_thread != nullptr) {
next_context = &current_thread->GetHostContext();
} else {
next_context = &idle_thread->GetHostContext();
}
Common::Fiber::YieldTo(switch_fiber, *next_context);
}
} }
} }
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
const u64 prev_switch_ticks = last_context_switch_time; const u64 prev_switch_ticks = last_context_switch_time;
const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks(); const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
if (thread != nullptr) { if (thread != nullptr) {
@ -510,6 +801,16 @@ void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
last_context_switch_time = most_recent_switch_ticks; last_context_switch_time = most_recent_switch_ticks;
} }
void Scheduler::Initialize() {
std::string name = "Idle Thread Id:" + std::to_string(core_id);
std::function<void(void*)> init_func = system.GetCpuManager().GetIdleThreadStartFunc();
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
nullptr, std::move(init_func), init_func_parameter);
idle_thread = std::move(thread_res).Unwrap();
}
void Scheduler::Shutdown() { void Scheduler::Shutdown() {
current_thread = nullptr; current_thread = nullptr;
selected_thread = nullptr; selected_thread = nullptr;
@ -538,4 +839,13 @@ SchedulerLockAndSleep::~SchedulerLockAndSleep() {
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds); time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
} }
void SchedulerLockAndSleep::Release() {
if (sleep_cancelled) {
return;
}
auto& time_manager = kernel.TimeManager();
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
sleep_cancelled = true;
}
} // namespace Kernel } // namespace Kernel

@ -11,9 +11,14 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "common/multi_level_queue.h" #include "common/multi_level_queue.h"
#include "common/spin_lock.h"
#include "core/hardware_properties.h" #include "core/hardware_properties.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
namespace Common {
class Fiber;
}
namespace Core { namespace Core {
class ARM_Interface; class ARM_Interface;
class System; class System;
@ -41,41 +46,17 @@ public:
return thread_list; return thread_list;
} }
/** /// Notify the scheduler a thread's status has changed.
* Add a thread to the suggested queue of a cpu core. Suggested threads may be void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags);
* picked if no thread is scheduled to run on the core.
*/ /// Notify the scheduler a thread's priority has changed.
void Suggest(u32 priority, std::size_t core, Thread* thread); void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority);
/// Notify the scheduler a thread's core and/or affinity mask has changed.
void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core);
/** /**
* Remove a thread to the suggested queue of a cpu core. Suggested threads may be * Takes care of selecting the new scheduled threads in three steps:
* picked if no thread is scheduled to run on the core.
*/
void Unsuggest(u32 priority, std::size_t core, Thread* thread);
/**
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
* back the queue in its priority level.
*/
void Schedule(u32 priority, std::size_t core, Thread* thread);
/**
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
* front the queue in its priority level.
*/
void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
/// Reschedule an already scheduled thread based on a new priority
void Reschedule(u32 priority, std::size_t core, Thread* thread);
/// Unschedules a thread.
void Unschedule(u32 priority, std::size_t core, Thread* thread);
/// Selects a core and forces it to unload its current thread's context
void UnloadThread(std::size_t core);
/**
* Takes care of selecting the new scheduled thread in three steps:
* *
* 1. First a thread is selected from the top of the priority queue. If no thread * 1. First a thread is selected from the top of the priority queue. If no thread
* is obtained then we move to step two, else we are done. * is obtained then we move to step two, else we are done.
@ -85,8 +66,10 @@ public:
* *
* 3. Third is no suggested thread is found, we do a second pass and pick a running * 3. Third is no suggested thread is found, we do a second pass and pick a running
* thread in another core and swap it with its current thread. * thread in another core and swap it with its current thread.
*
* returns the cores needing scheduling.
*/ */
void SelectThread(std::size_t core); u32 SelectThreads();
bool HaveReadyThreads(std::size_t core_id) const { bool HaveReadyThreads(std::size_t core_id) const {
return !scheduled_queue[core_id].empty(); return !scheduled_queue[core_id].empty();
@ -149,6 +132,40 @@ private:
/// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling /// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling
/// and reschedules current core if needed. /// and reschedules current core if needed.
void Unlock(); void Unlock();
void EnableInterruptAndSchedule(u32 cores_pending_reschedule,
Core::EmuThreadHandle global_thread);
/**
* Add a thread to the suggested queue of a cpu core. Suggested threads may be
* picked if no thread is scheduled to run on the core.
*/
void Suggest(u32 priority, std::size_t core, Thread* thread);
/**
* Remove a thread to the suggested queue of a cpu core. Suggested threads may be
* picked if no thread is scheduled to run on the core.
*/
void Unsuggest(u32 priority, std::size_t core, Thread* thread);
/**
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
* back the queue in its priority level.
*/
void Schedule(u32 priority, std::size_t core, Thread* thread);
/**
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
* front the queue in its priority level.
*/
void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
/// Reschedule an already scheduled thread based on a new priority
void Reschedule(u32 priority, std::size_t core, Thread* thread);
/// Unschedules a thread.
void Unschedule(u32 priority, std::size_t core, Thread* thread);
/** /**
* Transfers a thread into an specific core. If the destination_core is -1 * Transfers a thread into an specific core. If the destination_core is -1
* it will be unscheduled from its source code and added into its suggested * it will be unscheduled from its source code and added into its suggested
@ -170,10 +187,13 @@ private:
std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62}; std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
/// Scheduler lock mechanisms. /// Scheduler lock mechanisms.
std::mutex inner_lock{}; // TODO(Blinkhawk): Replace for a SpinLock bool is_locked{};
Common::SpinLock inner_lock{};
std::atomic<s64> scope_lock{}; std::atomic<s64> scope_lock{};
Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
Common::SpinLock global_list_guard{};
/// Lists all thread ids that aren't deleted/etc. /// Lists all thread ids that aren't deleted/etc.
std::vector<std::shared_ptr<Thread>> thread_list; std::vector<std::shared_ptr<Thread>> thread_list;
KernelCore& kernel; KernelCore& kernel;
@ -190,11 +210,11 @@ public:
/// 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 TryDoContextSwitch(); void TryDoContextSwitch();
/// Unloads currently running thread /// The next two are for SingleCore Only.
void UnloadThread(); /// Unload current thread before preempting core.
void Unload();
/// Select the threads in top of the scheduling multilist. /// Reload current thread after core preemption.
void SelectThreads(); void Reload();
/// Gets the current running thread /// Gets the current running thread
Thread* GetCurrentThread() const; Thread* GetCurrentThread() const;
@ -209,15 +229,30 @@ public:
return is_context_switch_pending; return is_context_switch_pending;
} }
void Initialize();
/// Shutdowns the scheduler. /// Shutdowns the scheduler.
void Shutdown(); void Shutdown();
void OnThreadStart();
std::shared_ptr<Common::Fiber>& ControlContext() {
return switch_fiber;
}
const std::shared_ptr<Common::Fiber>& ControlContext() const {
return switch_fiber;
}
private: private:
friend class GlobalScheduler; friend class GlobalScheduler;
/// Switches the CPU's active thread context to that of the specified thread /// Switches the CPU's active thread context to that of the specified thread
void SwitchContext(); void SwitchContext();
/// When a thread wakes up, it must run this through it's new scheduler
void SwitchContextStep2();
/** /**
* Called on every context switch to update the internal timestamp * Called on every context switch to update the internal timestamp
* This also updates the running time ticks for the given thread and * This also updates the running time ticks for the given thread and
@ -231,14 +266,24 @@ private:
*/ */
void UpdateLastContextSwitchTime(Thread* thread, Process* process); void UpdateLastContextSwitchTime(Thread* thread, Process* process);
static void OnSwitch(void* this_scheduler);
void SwitchToCurrent();
std::shared_ptr<Thread> current_thread = nullptr; std::shared_ptr<Thread> current_thread = nullptr;
std::shared_ptr<Thread> selected_thread = nullptr; std::shared_ptr<Thread> selected_thread = nullptr;
std::shared_ptr<Thread> current_thread_prev = nullptr;
std::shared_ptr<Thread> selected_thread_set = nullptr;
std::shared_ptr<Thread> idle_thread = nullptr;
std::shared_ptr<Common::Fiber> switch_fiber = nullptr;
Core::System& system; Core::System& system;
u64 last_context_switch_time = 0; u64 last_context_switch_time = 0;
u64 idle_selection_count = 0; u64 idle_selection_count = 0;
const std::size_t core_id; const std::size_t core_id;
Common::SpinLock guard{};
bool is_context_switch_pending = false; bool is_context_switch_pending = false;
}; };
@ -261,6 +306,8 @@ public:
sleep_cancelled = true; sleep_cancelled = true;
} }
void Release();
private: private:
Handle& event_handle; Handle& event_handle;
Thread* time_task; Thread* time_task;

@ -17,6 +17,7 @@
#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h" #include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h" #include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
@ -168,9 +169,12 @@ ResultCode ServerSession::CompleteSyncRequest() {
} }
// Some service requests require the thread to block // Some service requests require the thread to block
if (!context.IsThreadWaiting()) { {
context.GetThread().ResumeFromWait(); SchedulerLock lock(kernel);
context.GetThread().SetWaitSynchronizationResult(result); if (!context.IsThreadWaiting()) {
context.GetThread().ResumeFromWait();
context.GetThread().SetSynchronizationResults(nullptr, result);
}
} }
request_queue.Pop(); request_queue.Pop();
@ -180,8 +184,10 @@ ResultCode ServerSession::CompleteSyncRequest() {
ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
Core::Memory::Memory& memory) { Core::Memory::Memory& memory) {
Core::System::GetInstance().CoreTiming().ScheduleEvent(20000, request_event, {}); ResultCode result = QueueSyncRequest(std::move(thread), memory);
return QueueSyncRequest(std::move(thread), memory); const u64 delay = kernel.IsMulticore() ? 0U : 20000U;
Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {});
return result;
} }
} // namespace Kernel } // namespace Kernel

@ -10,14 +10,15 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/fiber.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/cpu_manager.h"
#include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/client_session.h"
@ -27,6 +28,7 @@
#include "core/hle/kernel/memory/memory_block.h" #include "core/hle/kernel/memory/memory_block.h"
#include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
@ -37,6 +39,7 @@
#include "core/hle/kernel/svc_wrap.h" #include "core/hle/kernel/svc_wrap.h"
#include "core/hle/kernel/synchronization.h" #include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/kernel/transfer_memory.h" #include "core/hle/kernel/transfer_memory.h"
#include "core/hle/kernel/writable_event.h" #include "core/hle/kernel/writable_event.h"
#include "core/hle/lock.h" #include "core/hle/lock.h"
@ -133,6 +136,7 @@ enum class ResourceLimitValueType {
ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_limit, ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_limit,
u32 resource_type, ResourceLimitValueType value_type) { u32 resource_type, ResourceLimitValueType value_type) {
std::lock_guard lock{HLE::g_hle_lock};
const auto type = static_cast<ResourceType>(resource_type); const auto type = static_cast<ResourceType>(resource_type);
if (!IsValidResourceType(type)) { if (!IsValidResourceType(type)) {
LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type); LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type);
@ -160,6 +164,7 @@ ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_
/// Set the process heap to a given Size. It can both extend and shrink the heap. /// Set the process heap to a given Size. It can both extend and shrink the heap.
static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_size) { static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_size) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size); LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size);
// Size must be a multiple of 0x200000 (2MB) and be equal to or less than 8GB. // Size must be a multiple of 0x200000 (2MB) and be equal to or less than 8GB.
@ -190,6 +195,7 @@ static ResultCode SetHeapSize32(Core::System& system, u32* heap_addr, u32 heap_s
static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask, static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 size, u32 mask,
u32 attribute) { u32 attribute) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, LOG_DEBUG(Kernel_SVC,
"called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address, "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address,
size, mask, attribute); size, mask, attribute);
@ -226,8 +232,15 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
static_cast<Memory::MemoryAttribute>(attribute)); static_cast<Memory::MemoryAttribute>(attribute));
} }
static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
u32 attribute) {
return SetMemoryAttribute(system, static_cast<VAddr>(address), static_cast<std::size_t>(size),
mask, attribute);
}
/// Maps a memory range into a different range. /// Maps a memory range into a different range.
static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size); src_addr, size);
@ -241,8 +254,14 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr
return page_table.Map(dst_addr, src_addr, size); return page_table.Map(dst_addr, src_addr, size);
} }
static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
return MapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
static_cast<std::size_t>(size));
}
/// Unmaps a region that was previously mapped with svcMapMemory /// Unmaps a region that was previously mapped with svcMapMemory
static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) { static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr, u64 size) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size); src_addr, size);
@ -256,9 +275,15 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad
return page_table.Unmap(dst_addr, src_addr, size); return page_table.Unmap(dst_addr, src_addr, size);
} }
static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
return UnmapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
static_cast<std::size_t>(size));
}
/// Connect to an OS service given the port name, returns the handle to the port to out /// Connect to an OS service given the port name, returns the handle to the port to out
static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle, static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
VAddr port_name_address) { VAddr port_name_address) {
std::lock_guard lock{HLE::g_hle_lock};
auto& memory = system.Memory(); auto& memory = system.Memory();
if (!memory.IsValidVirtualAddress(port_name_address)) { if (!memory.IsValidVirtualAddress(port_name_address)) {
@ -317,11 +342,30 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
auto thread = system.CurrentScheduler().GetCurrentThread(); auto thread = system.CurrentScheduler().GetCurrentThread();
thread->InvalidateWakeupCallback(); {
thread->SetStatus(ThreadStatus::WaitIPC); SchedulerLock lock(system.Kernel());
system.PrepareReschedule(thread->GetProcessorID()); thread->InvalidateHLECallback();
thread->SetStatus(ThreadStatus::WaitIPC);
session->SendSyncRequest(SharedFrom(thread), system.Memory());
}
return session->SendSyncRequest(SharedFrom(thread), system.Memory()); if (thread->HasHLECallback()) {
Handle event_handle = thread->GetHLETimeEvent();
if (event_handle != InvalidHandle) {
auto& time_manager = system.Kernel().TimeManager();
time_manager.UnscheduleTimeEvent(event_handle);
}
{
SchedulerLock lock(system.Kernel());
auto* sync_object = thread->GetHLESyncObject();
sync_object->RemoveWaitingThread(SharedFrom(thread));
}
thread->InvokeHLECallback(SharedFrom(thread));
}
return thread->GetSignalingResult();
} }
static ResultCode SendSyncRequest32(Core::System& system, Handle handle) { static ResultCode SendSyncRequest32(Core::System& system, Handle handle) {
@ -383,6 +427,15 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
} }
static ResultCode GetProcessId32(Core::System& system, u32* process_id_low, u32* process_id_high,
Handle handle) {
u64 process_id{};
const auto result = GetProcessId(system, &process_id, handle);
*process_id_low = static_cast<u32>(process_id);
*process_id_high = static_cast<u32>(process_id >> 32);
return result;
}
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds /// Wait for the given handles to synchronize, timeout after the specified nanoseconds
static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address, static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address,
u64 handle_count, s64 nano_seconds) { u64 handle_count, s64 nano_seconds) {
@ -447,10 +500,13 @@ static ResultCode CancelSynchronization(Core::System& system, Handle thread_hand
} }
thread->CancelWait(); thread->CancelWait();
system.PrepareReschedule(thread->GetProcessorID());
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode CancelSynchronization32(Core::System& system, Handle thread_handle) {
return CancelSynchronization(system, thread_handle);
}
/// Attempts to locks a mutex, creating it if it does not already exist /// Attempts to locks a mutex, creating it if it does not already exist
static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_handle, static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_handle,
VAddr mutex_addr, Handle requesting_thread_handle) { VAddr mutex_addr, Handle requesting_thread_handle) {
@ -475,6 +531,12 @@ static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_hand
requesting_thread_handle); requesting_thread_handle);
} }
static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle,
u32 mutex_addr, Handle requesting_thread_handle) {
return ArbitrateLock(system, holding_thread_handle, static_cast<VAddr>(mutex_addr),
requesting_thread_handle);
}
/// Unlock a mutex /// Unlock a mutex
static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) { static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr); LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr);
@ -494,6 +556,10 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
return current_process->GetMutex().Release(mutex_addr); return current_process->GetMutex().Release(mutex_addr);
} }
static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) {
return ArbitrateUnlock(system, static_cast<VAddr>(mutex_addr));
}
enum class BreakType : u32 { enum class BreakType : u32 {
Panic = 0, Panic = 0,
AssertionFailed = 1, AssertionFailed = 1,
@ -594,6 +660,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
if (!break_reason.signal_debugger) { if (!break_reason.signal_debugger) {
SchedulerLock lock(system.Kernel());
LOG_CRITICAL( LOG_CRITICAL(
Debug_Emulated, Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
@ -605,14 +672,16 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
const auto thread_processor_id = current_thread->GetProcessorID(); const auto thread_processor_id = current_thread->GetProcessorID();
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
system.Kernel().CurrentProcess()->PrepareForTermination();
// Kill the current thread // Kill the current thread
system.Kernel().ExceptionalExit();
current_thread->Stop(); current_thread->Stop();
system.PrepareReschedule();
} }
} }
static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) {
Break(system, reason, static_cast<u64>(info1), static_cast<u64>(info2));
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit /// Used to output a message on a debug hardware unit - does nothing on a retail unit
static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) { static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) {
if (len == 0) { if (len == 0) {
@ -627,6 +696,7 @@ static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr addre
/// Gets system/memory information for the current process /// Gets system/memory information for the current process
static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 handle, static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 handle,
u64 info_sub_id) { u64 info_sub_id) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle); info_sub_id, handle);
@ -863,9 +933,9 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks(); const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
out_ticks = thread_ticks + (core_timing.GetTicks() - prev_ctx_ticks); out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks);
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) { } else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
out_ticks = core_timing.GetTicks() - prev_ctx_ticks; out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks;
} }
*result = out_ticks; *result = out_ticks;
@ -892,6 +962,7 @@ static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_h
/// Maps memory at a desired address /// Maps memory at a desired address
static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
if (!Common::Is4KBAligned(addr)) { if (!Common::Is4KBAligned(addr)) {
@ -939,8 +1010,13 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
return page_table.MapPhysicalMemory(addr, size); return page_table.MapPhysicalMemory(addr, size);
} }
static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
return MapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
}
/// Unmaps memory previously mapped via MapPhysicalMemory /// Unmaps memory previously mapped via MapPhysicalMemory
static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
if (!Common::Is4KBAligned(addr)) { if (!Common::Is4KBAligned(addr)) {
@ -988,6 +1064,10 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
return page_table.UnmapPhysicalMemory(addr, size); return page_table.UnmapPhysicalMemory(addr, size);
} }
static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
return UnmapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
}
/// Sets the thread activity /// Sets the thread activity
static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) { static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity); LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity);
@ -1017,10 +1097,11 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act
return ERR_BUSY; return ERR_BUSY;
} }
thread->SetActivity(static_cast<ThreadActivity>(activity)); return thread->SetActivity(static_cast<ThreadActivity>(activity));
}
system.PrepareReschedule(thread->GetProcessorID()); static ResultCode SetThreadActivity32(Core::System& system, Handle handle, u32 activity) {
return RESULT_SUCCESS; return SetThreadActivity(system, handle, activity);
} }
/// Gets the thread context /// Gets the thread context
@ -1064,6 +1145,10 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) {
return GetThreadContext(system, static_cast<VAddr>(thread_context), handle);
}
/// Gets the priority for the specified thread /// Gets the priority for the specified thread
static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle handle) { static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle handle) {
LOG_TRACE(Kernel_SVC, "called"); LOG_TRACE(Kernel_SVC, "called");
@ -1071,6 +1156,7 @@ static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle); const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle);
if (!thread) { if (!thread) {
*priority = 0;
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
} }
@ -1105,18 +1191,26 @@ static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 pri
thread->SetPriority(priority); thread->SetPriority(priority);
system.PrepareReschedule(thread->GetProcessorID());
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode SetThreadPriority32(Core::System& system, Handle handle, u32 priority) {
return SetThreadPriority(system, handle, priority);
}
/// Get which CPU core is executing the current thread /// Get which CPU core is executing the current thread
static u32 GetCurrentProcessorNumber(Core::System& system) { static u32 GetCurrentProcessorNumber(Core::System& system) {
LOG_TRACE(Kernel_SVC, "called"); LOG_TRACE(Kernel_SVC, "called");
return system.CurrentScheduler().GetCurrentThread()->GetProcessorID(); return static_cast<u32>(system.CurrentPhysicalCore().CoreIndex());
}
static u32 GetCurrentProcessorNumber32(Core::System& system) {
return GetCurrentProcessorNumber(system);
} }
static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_handle, VAddr addr, static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_handle, VAddr addr,
u64 size, u32 permissions) { u64 size, u32 permissions) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, LOG_TRACE(Kernel_SVC,
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
shared_memory_handle, addr, size, permissions); shared_memory_handle, addr, size, permissions);
@ -1187,9 +1281,16 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
return shared_memory->Map(*current_process, addr, size, permission_type); return shared_memory->Map(*current_process, addr, size, permission_type);
} }
static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr,
u32 size, u32 permissions) {
return MapSharedMemory(system, shared_memory_handle, static_cast<VAddr>(addr),
static_cast<std::size_t>(size), permissions);
}
static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address, static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
VAddr page_info_address, Handle process_handle, VAddr page_info_address, Handle process_handle,
VAddr address) { VAddr address) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address); LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
std::shared_ptr<Process> process = handle_table.Get<Process>(process_handle); std::shared_ptr<Process> process = handle_table.Get<Process>(process_handle);
@ -1372,6 +1473,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
/// Exits the current process /// Exits the current process
static void ExitProcess(Core::System& system) { static void ExitProcess(Core::System& system) {
auto* current_process = system.Kernel().CurrentProcess(); auto* current_process = system.Kernel().CurrentProcess();
UNIMPLEMENTED();
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running, ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
@ -1381,8 +1483,10 @@ static void ExitProcess(Core::System& system) {
// Kill the current thread // Kill the current thread
system.CurrentScheduler().GetCurrentThread()->Stop(); system.CurrentScheduler().GetCurrentThread()->Stop();
}
system.PrepareReschedule(); static void ExitProcess32(Core::System& system) {
ExitProcess(system);
} }
/// Creates a new thread /// Creates a new thread
@ -1428,9 +1532,10 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Threads, 1)); ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Threads, 1));
ThreadType type = THREADTYPE_USER;
CASCADE_RESULT(std::shared_ptr<Thread> thread, CASCADE_RESULT(std::shared_ptr<Thread> thread,
Thread::Create(kernel, "", entry_point, priority, arg, processor_id, stack_top, Thread::Create(system, type, "", entry_point, priority, arg, processor_id,
*current_process)); stack_top, current_process));
const auto new_thread_handle = current_process->GetHandleTable().Create(thread); const auto new_thread_handle = current_process->GetHandleTable().Create(thread);
if (new_thread_handle.Failed()) { if (new_thread_handle.Failed()) {
@ -1444,11 +1549,15 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
thread->SetName( thread->SetName(
fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle)); fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle));
system.PrepareReschedule(thread->GetProcessorID());
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
return CreateThread(system, out_handle, static_cast<VAddr>(entry_point), static_cast<u64>(arg),
static_cast<VAddr>(stack_top), priority, processor_id);
}
/// Starts the thread for the provided handle /// Starts the thread for the provided handle
static ResultCode StartThread(Core::System& system, Handle thread_handle) { static ResultCode StartThread(Core::System& system, Handle thread_handle) {
LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
@ -1463,13 +1572,11 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
ASSERT(thread->GetStatus() == ThreadStatus::Dormant); ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
thread->ResumeFromWait(); return thread->Start();
}
if (thread->GetStatus() == ThreadStatus::Ready) { static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
system.PrepareReschedule(thread->GetProcessorID()); return StartThread(system, thread_handle);
}
return RESULT_SUCCESS;
} }
/// Called when a thread exits /// Called when a thread exits
@ -1477,9 +1584,12 @@ static void ExitThread(Core::System& system) {
LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
current_thread->Stop();
system.GlobalScheduler().RemoveThread(SharedFrom(current_thread)); system.GlobalScheduler().RemoveThread(SharedFrom(current_thread));
system.PrepareReschedule(); current_thread->Stop();
}
static void ExitThread32(Core::System& system) {
ExitThread(system);
} }
/// Sleep the current thread /// Sleep the current thread
@ -1498,15 +1608,21 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
if (nanoseconds <= 0) { if (nanoseconds <= 0) {
switch (static_cast<SleepType>(nanoseconds)) { switch (static_cast<SleepType>(nanoseconds)) {
case SleepType::YieldWithoutLoadBalancing: case SleepType::YieldWithoutLoadBalancing: {
is_redundant = current_thread->YieldSimple(); auto pair = current_thread->YieldSimple();
is_redundant = pair.second;
break; break;
case SleepType::YieldWithLoadBalancing: }
is_redundant = current_thread->YieldAndBalanceLoad(); case SleepType::YieldWithLoadBalancing: {
auto pair = current_thread->YieldAndBalanceLoad();
is_redundant = pair.second;
break; break;
case SleepType::YieldAndWaitForLoadBalancing: }
is_redundant = current_thread->YieldAndWaitForLoadBalancing(); case SleepType::YieldAndWaitForLoadBalancing: {
auto pair = current_thread->YieldAndWaitForLoadBalancing();
is_redundant = pair.second;
break; break;
}
default: default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
} }
@ -1514,13 +1630,18 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
current_thread->Sleep(nanoseconds); current_thread->Sleep(nanoseconds);
} }
if (is_redundant) { if (is_redundant && !system.Kernel().IsMulticore()) {
// If it's redundant, the core is pretty much idle. Some games keep idling system.Kernel().ExitSVCProfile();
// a core while it's doing nothing, we advance timing to avoid costly continuous system.CoreTiming().AddTicks(1000U);
// calls. system.GetCpuManager().PreemptSingleCore();
system.CoreTiming().AddTicks(2000); system.Kernel().EnterSVCProfile();
} }
system.PrepareReschedule(current_thread->GetProcessorID()); }
static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) {
const s64 nanoseconds = static_cast<s64>(static_cast<u64>(nanoseconds_low) |
(static_cast<u64>(nanoseconds_high) << 32));
SleepThread(system, nanoseconds);
} }
/// Wait process wide key atomic /// Wait process wide key atomic
@ -1547,31 +1668,69 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
} }
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4)); ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
auto& kernel = system.Kernel();
Handle event_handle;
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
auto* const current_process = system.Kernel().CurrentProcess(); auto* const current_process = system.Kernel().CurrentProcess();
const auto& handle_table = current_process->GetHandleTable(); {
std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle); SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
ASSERT(thread); const auto& handle_table = current_process->GetHandleTable();
std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
ASSERT(thread);
const auto release_result = current_process->GetMutex().Release(mutex_addr); current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
if (release_result.IsError()) {
return release_result; if (thread->IsPendingTermination()) {
lock.CancelSleep();
return ERR_THREAD_TERMINATING;
}
const auto release_result = current_process->GetMutex().Release(mutex_addr);
if (release_result.IsError()) {
lock.CancelSleep();
return release_result;
}
if (nano_seconds == 0) {
lock.CancelSleep();
return RESULT_TIMEOUT;
}
current_thread->SetCondVarWaitAddress(condition_variable_addr);
current_thread->SetMutexWaitAddress(mutex_addr);
current_thread->SetWaitHandle(thread_handle);
current_thread->SetStatus(ThreadStatus::WaitCondVar);
current_process->InsertConditionVariableThread(SharedFrom(current_thread));
} }
Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); if (event_handle != InvalidHandle) {
current_thread->SetCondVarWaitAddress(condition_variable_addr); auto& time_manager = kernel.TimeManager();
current_thread->SetMutexWaitAddress(mutex_addr); time_manager.UnscheduleTimeEvent(event_handle);
current_thread->SetWaitHandle(thread_handle); }
current_thread->SetStatus(ThreadStatus::WaitCondVar);
current_thread->InvalidateWakeupCallback();
current_process->InsertConditionVariableThread(SharedFrom(current_thread));
current_thread->WakeAfterDelay(nano_seconds); {
SchedulerLock lock(kernel);
auto* owner = current_thread->GetLockOwner();
if (owner != nullptr) {
owner->RemoveMutexWaiter(SharedFrom(current_thread));
}
current_process->RemoveConditionVariableThread(SharedFrom(current_thread));
}
// Note: Deliberately don't attempt to inherit the lock owner's priority. // Note: Deliberately don't attempt to inherit the lock owner's priority.
system.PrepareReschedule(current_thread->GetProcessorID()); return current_thread->GetSignalingResult();
return RESULT_SUCCESS; }
static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr,
u32 condition_variable_addr, Handle thread_handle,
u32 nanoseconds_low, u32 nanoseconds_high) {
const s64 nanoseconds =
static_cast<s64>(nanoseconds_low | (static_cast<u64>(nanoseconds_high) << 32));
return WaitProcessWideKeyAtomic(system, static_cast<VAddr>(mutex_addr),
static_cast<VAddr>(condition_variable_addr), thread_handle,
nanoseconds);
} }
/// Signal process wide key /// Signal process wide key
@ -1582,7 +1741,9 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4)); ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
// Retrieve a list of all threads that are waiting for this condition variable. // Retrieve a list of all threads that are waiting for this condition variable.
auto* const current_process = system.Kernel().CurrentProcess(); auto& kernel = system.Kernel();
SchedulerLock lock(kernel);
auto* const current_process = kernel.CurrentProcess();
std::vector<std::shared_ptr<Thread>> waiting_threads = std::vector<std::shared_ptr<Thread>> waiting_threads =
current_process->GetConditionVariableThreads(condition_variable_addr); current_process->GetConditionVariableThreads(condition_variable_addr);
@ -1591,7 +1752,7 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
std::size_t last = waiting_threads.size(); std::size_t last = waiting_threads.size();
if (target > 0) if (target > 0)
last = std::min(waiting_threads.size(), static_cast<std::size_t>(target)); last = std::min(waiting_threads.size(), static_cast<std::size_t>(target));
auto& time_manager = kernel.TimeManager();
for (std::size_t index = 0; index < last; ++index) { for (std::size_t index = 0; index < last; ++index) {
auto& thread = waiting_threads[index]; auto& thread = waiting_threads[index];
@ -1599,7 +1760,6 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
// liberate Cond Var Thread. // liberate Cond Var Thread.
current_process->RemoveConditionVariableThread(thread); current_process->RemoveConditionVariableThread(thread);
thread->SetCondVarWaitAddress(0);
const std::size_t current_core = system.CurrentCoreIndex(); const std::size_t current_core = system.CurrentCoreIndex();
auto& monitor = system.Monitor(); auto& monitor = system.Monitor();
@ -1610,10 +1770,8 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
u32 update_val = 0; u32 update_val = 0;
const VAddr mutex_address = thread->GetMutexWaitAddress(); const VAddr mutex_address = thread->GetMutexWaitAddress();
do { do {
monitor.SetExclusive(current_core, mutex_address);
// If the mutex is not yet acquired, acquire it. // If the mutex is not yet acquired, acquire it.
mutex_val = memory.Read32(mutex_address); mutex_val = monitor.ExclusiveRead32(current_core, mutex_address);
if (mutex_val != 0) { if (mutex_val != 0) {
update_val = mutex_val | Mutex::MutexHasWaitersFlag; update_val = mutex_val | Mutex::MutexHasWaitersFlag;
@ -1621,33 +1779,28 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
update_val = thread->GetWaitHandle(); update_val = thread->GetWaitHandle();
} }
} while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val)); } while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val));
monitor.ClearExclusive();
if (mutex_val == 0) { if (mutex_val == 0) {
// We were able to acquire the mutex, resume this thread. // We were able to acquire the mutex, resume this thread.
ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->ResumeFromWait();
auto* const lock_owner = thread->GetLockOwner(); auto* const lock_owner = thread->GetLockOwner();
if (lock_owner != nullptr) { if (lock_owner != nullptr) {
lock_owner->RemoveMutexWaiter(thread); lock_owner->RemoveMutexWaiter(thread);
} }
thread->SetLockOwner(nullptr); thread->SetLockOwner(nullptr);
thread->SetMutexWaitAddress(0); thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
thread->SetWaitHandle(0); thread->ResumeFromWait();
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
system.PrepareReschedule(thread->GetProcessorID());
} else { } else {
// The mutex is already owned by some other thread, make this thread wait on it. // The mutex is already owned by some other thread, make this thread wait on it.
const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
auto owner = handle_table.Get<Thread>(owner_handle); auto owner = handle_table.Get<Thread>(owner_handle);
ASSERT(owner); ASSERT(owner);
ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar); if (thread->GetStatus() == ThreadStatus::WaitCondVar) {
thread->InvalidateWakeupCallback(); thread->SetStatus(ThreadStatus::WaitMutex);
thread->SetStatus(ThreadStatus::WaitMutex); }
owner->AddMutexWaiter(thread); owner->AddMutexWaiter(thread);
system.PrepareReschedule(thread->GetProcessorID());
} }
} }
} }
@ -1678,12 +1831,15 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter(); auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
const ResultCode result = const ResultCode result =
address_arbiter.WaitForAddress(address, arbitration_type, value, timeout); address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
if (result == RESULT_SUCCESS) {
system.PrepareReschedule();
}
return result; return result;
} }
static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value,
u32 timeout_low, u32 timeout_high) {
s64 timeout = static_cast<s64>(timeout_low | (static_cast<u64>(timeout_high) << 32));
return WaitForAddress(system, static_cast<VAddr>(address), type, value, timeout);
}
// Signals to an address (via Address Arbiter) // Signals to an address (via Address Arbiter)
static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value, static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value,
s32 num_to_wake) { s32 num_to_wake) {
@ -1707,6 +1863,11 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type,
return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake); return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake);
} }
static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value,
s32 num_to_wake) {
return SignalToAddress(system, static_cast<VAddr>(address), type, value, num_to_wake);
}
static void KernelDebug([[maybe_unused]] Core::System& system, static void KernelDebug([[maybe_unused]] Core::System& system,
[[maybe_unused]] u32 kernel_debug_type, [[maybe_unused]] u64 param1, [[maybe_unused]] u32 kernel_debug_type, [[maybe_unused]] u64 param1,
[[maybe_unused]] u64 param2, [[maybe_unused]] u64 param3) { [[maybe_unused]] u64 param2, [[maybe_unused]] u64 param3) {
@ -1725,14 +1886,21 @@ static u64 GetSystemTick(Core::System& system) {
auto& core_timing = system.CoreTiming(); auto& core_timing = system.CoreTiming();
// Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick) // Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
const u64 result{Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks())}; const u64 result{system.CoreTiming().GetClockTicks()};
// Advance time to defeat dumb games that busy-wait for the frame to end. if (!system.Kernel().IsMulticore()) {
core_timing.AddTicks(400); core_timing.AddTicks(400U);
}
return result; return result;
} }
static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) {
u64 time = GetSystemTick(system);
*time_low = static_cast<u32>(time);
*time_high = static_cast<u32>(time >> 32);
}
/// Close a handle /// Close a handle
static ResultCode CloseHandle(Core::System& system, Handle handle) { static ResultCode CloseHandle(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
@ -1765,9 +1933,14 @@ static ResultCode ResetSignal(Core::System& system, Handle handle) {
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
} }
static ResultCode ResetSignal32(Core::System& system, Handle handle) {
return ResetSignal(system, handle);
}
/// Creates a TransferMemory object /// Creates a TransferMemory object
static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAddr addr, u64 size, static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAddr addr, u64 size,
u32 permissions) { u32 permissions) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size,
permissions); permissions);
@ -1812,6 +1985,12 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size,
u32 permissions) {
return CreateTransferMemory(system, handle, static_cast<VAddr>(addr),
static_cast<std::size_t>(size), permissions);
}
static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core, static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core,
u64* mask) { u64* mask) {
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
@ -1821,6 +2000,8 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
if (!thread) { if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}", LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
thread_handle); thread_handle);
*core = 0;
*mask = 0;
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
} }
@ -1830,6 +2011,15 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, u32* core,
u32* mask_low, u32* mask_high) {
u64 mask{};
const auto result = GetThreadCoreMask(system, thread_handle, core, &mask);
*mask_high = static_cast<u32>(mask >> 32);
*mask_low = static_cast<u32>(mask);
return result;
}
static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core, static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core,
u64 affinity_mask) { u64 affinity_mask) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core=0x{:X}, affinity_mask=0x{:016X}", LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core=0x{:X}, affinity_mask=0x{:016X}",
@ -1861,7 +2051,7 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
return ERR_INVALID_COMBINATION; return ERR_INVALID_COMBINATION;
} }
if (core < Core::NUM_CPU_CORES) { if (core < Core::Hardware::NUM_CPU_CORES) {
if ((affinity_mask & (1ULL << core)) == 0) { if ((affinity_mask & (1ULL << core)) == 0) {
LOG_ERROR(Kernel_SVC, LOG_ERROR(Kernel_SVC,
"Core is not enabled for the current mask, core={}, mask={:016X}", core, "Core is not enabled for the current mask, core={}, mask={:016X}", core,
@ -1883,11 +2073,14 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
} }
system.PrepareReschedule(thread->GetProcessorID()); return thread->SetCoreAndAffinityMask(core, affinity_mask);
thread->ChangeCore(core, affinity_mask); }
system.PrepareReschedule(thread->GetProcessorID());
return RESULT_SUCCESS; static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core,
u32 affinity_mask_low, u32 affinity_mask_high) {
const u64 affinity_mask =
static_cast<u64>(affinity_mask_low) | (static_cast<u64>(affinity_mask_high) << 32);
return SetThreadCoreMask(system, thread_handle, core, affinity_mask);
} }
static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle* read_handle) { static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle* read_handle) {
@ -1918,6 +2111,10 @@ static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode CreateEvent32(Core::System& system, Handle* write_handle, Handle* read_handle) {
return CreateEvent(system, write_handle, read_handle);
}
static ResultCode ClearEvent(Core::System& system, Handle handle) { static ResultCode ClearEvent(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle); LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
@ -1939,6 +2136,10 @@ static ResultCode ClearEvent(Core::System& system, Handle handle) {
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
} }
static ResultCode ClearEvent32(Core::System& system, Handle handle) {
return ClearEvent(system, handle);
}
static ResultCode SignalEvent(Core::System& system, Handle handle) { static ResultCode SignalEvent(Core::System& system, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle); LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle);
@ -1951,10 +2152,13 @@ static ResultCode SignalEvent(Core::System& system, Handle handle) {
} }
writable_event->Signal(); writable_event->Signal();
system.PrepareReschedule();
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode SignalEvent32(Core::System& system, Handle handle) {
return SignalEvent(system, handle);
}
static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) { static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
@ -1982,6 +2186,7 @@ static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_
} }
static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle) { static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle) {
std::lock_guard lock{HLE::g_hle_lock};
LOG_DEBUG(Kernel_SVC, "called"); LOG_DEBUG(Kernel_SVC, "called");
auto& kernel = system.Kernel(); auto& kernel = system.Kernel();
@ -2139,6 +2344,15 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address,
u32 size) {
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope
// as all emulation is done in the same cache level in host architecture, thus data cache
// does not need flushing.
LOG_DEBUG(Kernel_SVC, "called");
return RESULT_SUCCESS;
}
namespace { namespace {
struct FunctionDef { struct FunctionDef {
using Func = void(Core::System&); using Func = void(Core::System&);
@ -2153,57 +2367,57 @@ static const FunctionDef SVC_Table_32[] = {
{0x00, nullptr, "Unknown"}, {0x00, nullptr, "Unknown"},
{0x01, SvcWrap32<SetHeapSize32>, "SetHeapSize32"}, {0x01, SvcWrap32<SetHeapSize32>, "SetHeapSize32"},
{0x02, nullptr, "Unknown"}, {0x02, nullptr, "Unknown"},
{0x03, nullptr, "SetMemoryAttribute32"}, {0x03, SvcWrap32<SetMemoryAttribute32>, "SetMemoryAttribute32"},
{0x04, nullptr, "MapMemory32"}, {0x04, SvcWrap32<MapMemory32>, "MapMemory32"},
{0x05, nullptr, "UnmapMemory32"}, {0x05, SvcWrap32<UnmapMemory32>, "UnmapMemory32"},
{0x06, SvcWrap32<QueryMemory32>, "QueryMemory32"}, {0x06, SvcWrap32<QueryMemory32>, "QueryMemory32"},
{0x07, nullptr, "ExitProcess32"}, {0x07, SvcWrap32<ExitProcess32>, "ExitProcess32"},
{0x08, nullptr, "CreateThread32"}, {0x08, SvcWrap32<CreateThread32>, "CreateThread32"},
{0x09, nullptr, "StartThread32"}, {0x09, SvcWrap32<StartThread32>, "StartThread32"},
{0x0a, nullptr, "ExitThread32"}, {0x0a, SvcWrap32<ExitThread32>, "ExitThread32"},
{0x0b, nullptr, "SleepThread32"}, {0x0b, SvcWrap32<SleepThread32>, "SleepThread32"},
{0x0c, SvcWrap32<GetThreadPriority32>, "GetThreadPriority32"}, {0x0c, SvcWrap32<GetThreadPriority32>, "GetThreadPriority32"},
{0x0d, nullptr, "SetThreadPriority32"}, {0x0d, SvcWrap32<SetThreadPriority32>, "SetThreadPriority32"},
{0x0e, nullptr, "GetThreadCoreMask32"}, {0x0e, SvcWrap32<GetThreadCoreMask32>, "GetThreadCoreMask32"},
{0x0f, nullptr, "SetThreadCoreMask32"}, {0x0f, SvcWrap32<SetThreadCoreMask32>, "SetThreadCoreMask32"},
{0x10, nullptr, "GetCurrentProcessorNumber32"}, {0x10, SvcWrap32<GetCurrentProcessorNumber32>, "GetCurrentProcessorNumber32"},
{0x11, nullptr, "SignalEvent32"}, {0x11, SvcWrap32<SignalEvent32>, "SignalEvent32"},
{0x12, nullptr, "ClearEvent32"}, {0x12, SvcWrap32<ClearEvent32>, "ClearEvent32"},
{0x13, nullptr, "MapSharedMemory32"}, {0x13, SvcWrap32<MapSharedMemory32>, "MapSharedMemory32"},
{0x14, nullptr, "UnmapSharedMemory32"}, {0x14, nullptr, "UnmapSharedMemory32"},
{0x15, nullptr, "CreateTransferMemory32"}, {0x15, SvcWrap32<CreateTransferMemory32>, "CreateTransferMemory32"},
{0x16, SvcWrap32<CloseHandle32>, "CloseHandle32"}, {0x16, SvcWrap32<CloseHandle32>, "CloseHandle32"},
{0x17, nullptr, "ResetSignal32"}, {0x17, SvcWrap32<ResetSignal32>, "ResetSignal32"},
{0x18, SvcWrap32<WaitSynchronization32>, "WaitSynchronization32"}, {0x18, SvcWrap32<WaitSynchronization32>, "WaitSynchronization32"},
{0x19, nullptr, "CancelSynchronization32"}, {0x19, SvcWrap32<CancelSynchronization32>, "CancelSynchronization32"},
{0x1a, nullptr, "ArbitrateLock32"}, {0x1a, SvcWrap32<ArbitrateLock32>, "ArbitrateLock32"},
{0x1b, nullptr, "ArbitrateUnlock32"}, {0x1b, SvcWrap32<ArbitrateUnlock32>, "ArbitrateUnlock32"},
{0x1c, nullptr, "WaitProcessWideKeyAtomic32"}, {0x1c, SvcWrap32<WaitProcessWideKeyAtomic32>, "WaitProcessWideKeyAtomic32"},
{0x1d, SvcWrap32<SignalProcessWideKey32>, "SignalProcessWideKey32"}, {0x1d, SvcWrap32<SignalProcessWideKey32>, "SignalProcessWideKey32"},
{0x1e, nullptr, "GetSystemTick32"}, {0x1e, SvcWrap32<GetSystemTick32>, "GetSystemTick32"},
{0x1f, SvcWrap32<ConnectToNamedPort32>, "ConnectToNamedPort32"}, {0x1f, SvcWrap32<ConnectToNamedPort32>, "ConnectToNamedPort32"},
{0x20, nullptr, "Unknown"}, {0x20, nullptr, "Unknown"},
{0x21, SvcWrap32<SendSyncRequest32>, "SendSyncRequest32"}, {0x21, SvcWrap32<SendSyncRequest32>, "SendSyncRequest32"},
{0x22, nullptr, "SendSyncRequestWithUserBuffer32"}, {0x22, nullptr, "SendSyncRequestWithUserBuffer32"},
{0x23, nullptr, "Unknown"}, {0x23, nullptr, "Unknown"},
{0x24, nullptr, "GetProcessId32"}, {0x24, SvcWrap32<GetProcessId32>, "GetProcessId32"},
{0x25, SvcWrap32<GetThreadId32>, "GetThreadId32"}, {0x25, SvcWrap32<GetThreadId32>, "GetThreadId32"},
{0x26, nullptr, "Break32"}, {0x26, SvcWrap32<Break32>, "Break32"},
{0x27, nullptr, "OutputDebugString32"}, {0x27, nullptr, "OutputDebugString32"},
{0x28, nullptr, "Unknown"}, {0x28, nullptr, "Unknown"},
{0x29, SvcWrap32<GetInfo32>, "GetInfo32"}, {0x29, SvcWrap32<GetInfo32>, "GetInfo32"},
{0x2a, nullptr, "Unknown"}, {0x2a, nullptr, "Unknown"},
{0x2b, nullptr, "Unknown"}, {0x2b, nullptr, "Unknown"},
{0x2c, nullptr, "MapPhysicalMemory32"}, {0x2c, SvcWrap32<MapPhysicalMemory32>, "MapPhysicalMemory32"},
{0x2d, nullptr, "UnmapPhysicalMemory32"}, {0x2d, SvcWrap32<UnmapPhysicalMemory32>, "UnmapPhysicalMemory32"},
{0x2e, nullptr, "Unknown"}, {0x2e, nullptr, "Unknown"},
{0x2f, nullptr, "Unknown"}, {0x2f, nullptr, "Unknown"},
{0x30, nullptr, "Unknown"}, {0x30, nullptr, "Unknown"},
{0x31, nullptr, "Unknown"}, {0x31, nullptr, "Unknown"},
{0x32, nullptr, "SetThreadActivity32"}, {0x32, SvcWrap32<SetThreadActivity32>, "SetThreadActivity32"},
{0x33, nullptr, "GetThreadContext32"}, {0x33, SvcWrap32<GetThreadContext32>, "GetThreadContext32"},
{0x34, nullptr, "WaitForAddress32"}, {0x34, SvcWrap32<WaitForAddress32>, "WaitForAddress32"},
{0x35, nullptr, "SignalToAddress32"}, {0x35, SvcWrap32<SignalToAddress32>, "SignalToAddress32"},
{0x36, nullptr, "Unknown"}, {0x36, nullptr, "Unknown"},
{0x37, nullptr, "Unknown"}, {0x37, nullptr, "Unknown"},
{0x38, nullptr, "Unknown"}, {0x38, nullptr, "Unknown"},
@ -2219,7 +2433,7 @@ static const FunctionDef SVC_Table_32[] = {
{0x42, nullptr, "Unknown"}, {0x42, nullptr, "Unknown"},
{0x43, nullptr, "ReplyAndReceive32"}, {0x43, nullptr, "ReplyAndReceive32"},
{0x44, nullptr, "Unknown"}, {0x44, nullptr, "Unknown"},
{0x45, nullptr, "CreateEvent32"}, {0x45, SvcWrap32<CreateEvent32>, "CreateEvent32"},
{0x46, nullptr, "Unknown"}, {0x46, nullptr, "Unknown"},
{0x47, nullptr, "Unknown"}, {0x47, nullptr, "Unknown"},
{0x48, nullptr, "Unknown"}, {0x48, nullptr, "Unknown"},
@ -2245,7 +2459,7 @@ static const FunctionDef SVC_Table_32[] = {
{0x5c, nullptr, "Unknown"}, {0x5c, nullptr, "Unknown"},
{0x5d, nullptr, "Unknown"}, {0x5d, nullptr, "Unknown"},
{0x5e, nullptr, "Unknown"}, {0x5e, nullptr, "Unknown"},
{0x5F, nullptr, "FlushProcessDataCache32"}, {0x5F, SvcWrap32<FlushProcessDataCache32>, "FlushProcessDataCache32"},
{0x60, nullptr, "Unknown"}, {0x60, nullptr, "Unknown"},
{0x61, nullptr, "Unknown"}, {0x61, nullptr, "Unknown"},
{0x62, nullptr, "Unknown"}, {0x62, nullptr, "Unknown"},
@ -2423,13 +2637,10 @@ static const FunctionDef* GetSVCInfo64(u32 func_num) {
return &SVC_Table_64[func_num]; return &SVC_Table_64[func_num];
} }
MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
void Call(Core::System& system, u32 immediate) { void Call(Core::System& system, u32 immediate) {
MICROPROFILE_SCOPE(Kernel_SVC); system.ExitDynarmicProfile();
auto& kernel = system.Kernel();
// Lock the global kernel mutex when we enter the kernel HLE. kernel.EnterSVCProfile();
std::lock_guard lock{HLE::g_hle_lock};
const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate) const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate)
: GetSVCInfo32(immediate); : GetSVCInfo32(immediate);
@ -2442,6 +2653,9 @@ void Call(Core::System& system, u32 immediate) {
} else { } else {
LOG_CRITICAL(Kernel_SVC, "Unknown SVC function 0x{:X}", immediate); LOG_CRITICAL(Kernel_SVC, "Unknown SVC function 0x{:X}", immediate);
} }
kernel.ExitSVCProfile();
system.EnterDynarmicProfile();
} }
} // namespace Kernel::Svc } // namespace Kernel::Svc

@ -350,13 +350,50 @@ void SvcWrap64(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2)); func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2));
} }
// Used by QueryMemory32 // Used by QueryMemory32, ArbitrateLock32
template <ResultCode func(Core::System&, u32, u32, u32)> template <ResultCode func(Core::System&, u32, u32, u32)>
void SvcWrap32(Core::System& system) { void SvcWrap32(Core::System& system) {
FuncReturn32(system, FuncReturn32(system,
func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2)).raw); func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2)).raw);
} }
// Used by Break32
template <void func(Core::System&, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2));
}
// Used by ExitProcess32, ExitThread32
template <void func(Core::System&)>
void SvcWrap32(Core::System& system) {
func(system);
}
// Used by GetCurrentProcessorNumber32
template <u32 func(Core::System&)>
void SvcWrap32(Core::System& system) {
FuncReturn32(system, func(system));
}
// Used by SleepThread32
template <void func(Core::System&, u32, u32)>
void SvcWrap32(Core::System& system) {
func(system, Param32(system, 0), Param32(system, 1));
}
// Used by CreateThread32
template <ResultCode func(Core::System&, Handle*, u32, u32, u32, u32, s32)>
void SvcWrap32(Core::System& system) {
Handle param_1 = 0;
const u32 retval = func(system, &param_1, Param32(system, 0), Param32(system, 1),
Param32(system, 2), Param32(system, 3), Param32(system, 4))
.raw;
system.CurrentArmInterface().SetReg(1, param_1);
FuncReturn(system, retval);
}
// Used by GetInfo32 // Used by GetInfo32
template <ResultCode func(Core::System&, u32*, u32*, u32, u32, u32, u32)> template <ResultCode func(Core::System&, u32*, u32*, u32, u32, u32, u32)>
void SvcWrap32(Core::System& system) { void SvcWrap32(Core::System& system) {
@ -393,18 +430,114 @@ void SvcWrap32(Core::System& system) {
FuncReturn(system, retval); FuncReturn(system, retval);
} }
// Used by GetSystemTick32
template <void func(Core::System&, u32*, u32*)>
void SvcWrap32(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
func(system, &param_1, &param_2);
system.CurrentArmInterface().SetReg(0, param_1);
system.CurrentArmInterface().SetReg(1, param_2);
}
// Used by CreateEvent32
template <ResultCode func(Core::System&, Handle*, Handle*)>
void SvcWrap32(Core::System& system) {
Handle param_1 = 0;
Handle param_2 = 0;
const u32 retval = func(system, &param_1, &param_2).raw;
system.CurrentArmInterface().SetReg(1, param_1);
system.CurrentArmInterface().SetReg(2, param_2);
FuncReturn(system, retval);
}
// Used by GetThreadId32
template <ResultCode func(Core::System&, Handle, u32*, u32*, u32*)>
void SvcWrap32(Core::System& system) {
u32 param_1 = 0;
u32 param_2 = 0;
u32 param_3 = 0;
const u32 retval = func(system, Param32(system, 2), &param_1, &param_2, &param_3).raw;
system.CurrentArmInterface().SetReg(1, param_1);
system.CurrentArmInterface().SetReg(2, param_2);
system.CurrentArmInterface().SetReg(3, param_3);
FuncReturn(system, retval);
}
// Used by SignalProcessWideKey32 // Used by SignalProcessWideKey32
template <void func(Core::System&, u32, s32)> template <void func(Core::System&, u32, s32)>
void SvcWrap32(Core::System& system) { void SvcWrap32(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), static_cast<s32>(Param(system, 1))); func(system, static_cast<u32>(Param(system, 0)), static_cast<s32>(Param(system, 1)));
} }
// Used by SendSyncRequest32 // Used by SetThreadPriority32
template <ResultCode func(Core::System&, Handle, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw;
FuncReturn(system, retval);
}
// Used by SetThreadCoreMask32
template <ResultCode func(Core::System&, Handle, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<Handle>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3)))
.raw;
FuncReturn(system, retval);
}
// Used by WaitProcessWideKeyAtomic32
template <ResultCode func(Core::System&, u32, u32, Handle, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
static_cast<Handle>(Param(system, 2)), static_cast<u32>(Param(system, 3)),
static_cast<u32>(Param(system, 4)))
.raw;
FuncReturn(system, retval);
}
// Used by WaitForAddress32
template <ResultCode func(Core::System&, u32, u32, s32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval = func(system, static_cast<u32>(Param(system, 0)),
static_cast<u32>(Param(system, 1)), static_cast<s32>(Param(system, 2)),
static_cast<u32>(Param(system, 3)), static_cast<u32>(Param(system, 4)))
.raw;
FuncReturn(system, retval);
}
// Used by SignalToAddress32
template <ResultCode func(Core::System&, u32, u32, s32, s32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
.raw;
FuncReturn(system, retval);
}
// Used by SendSyncRequest32, ArbitrateUnlock32
template <ResultCode func(Core::System&, u32)> template <ResultCode func(Core::System&, u32)>
void SvcWrap32(Core::System& system) { void SvcWrap32(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw); FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
} }
// Used by CreateTransferMemory32
template <ResultCode func(Core::System&, Handle*, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
Handle handle = 0;
const u32 retval =
func(system, &handle, Param32(system, 1), Param32(system, 2), Param32(system, 3)).raw;
system.CurrentArmInterface().SetReg(1, handle);
FuncReturn(system, retval);
}
// Used by WaitSynchronization32 // Used by WaitSynchronization32
template <ResultCode func(Core::System&, u32, u32, s32, u32, Handle*)> template <ResultCode func(Core::System&, u32, u32, s32, u32, Handle*)>
void SvcWrap32(Core::System& system) { void SvcWrap32(Core::System& system) {

@ -10,78 +10,107 @@
#include "core/hle/kernel/synchronization.h" #include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/synchronization_object.h" #include "core/hle/kernel/synchronization_object.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel { namespace Kernel {
/// Default thread wakeup callback for WaitSynchronization
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<SynchronizationObject> object,
std::size_t index) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
if (reason == ThreadWakeupReason::Timeout) {
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
return true;
}
ASSERT(reason == ThreadWakeupReason::Signal);
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
thread->SetWaitSynchronizationOutput(static_cast<u32>(index));
return true;
}
Synchronization::Synchronization(Core::System& system) : system{system} {} Synchronization::Synchronization(Core::System& system) : system{system} {}
void Synchronization::SignalObject(SynchronizationObject& obj) const { void Synchronization::SignalObject(SynchronizationObject& obj) const {
auto& kernel = system.Kernel();
SchedulerLock lock(kernel);
auto& time_manager = kernel.TimeManager();
if (obj.IsSignaled()) { if (obj.IsSignaled()) {
obj.WakeupAllWaitingThreads(); for (auto thread : obj.GetWaitingThreads()) {
if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) {
if (thread->GetStatus() != ThreadStatus::WaitHLEEvent) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
ASSERT(thread->IsWaitingSync());
}
thread->SetSynchronizationResults(&obj, RESULT_SUCCESS);
thread->ResumeFromWait();
}
}
obj.ClearWaitingThreads();
} }
} }
std::pair<ResultCode, Handle> Synchronization::WaitFor( std::pair<ResultCode, Handle> Synchronization::WaitFor(
std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) { std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
auto& kernel = system.Kernel();
auto* const thread = system.CurrentScheduler().GetCurrentThread(); auto* const thread = system.CurrentScheduler().GetCurrentThread();
// Find the first object that is acquirable in the provided list of objects Handle event_handle = InvalidHandle;
const auto itr = std::find_if(sync_objects.begin(), sync_objects.end(), {
[thread](const std::shared_ptr<SynchronizationObject>& object) { SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
return object->IsSignaled(); const auto itr =
}); std::find_if(sync_objects.begin(), sync_objects.end(),
[thread](const std::shared_ptr<SynchronizationObject>& object) {
return object->IsSignaled();
});
if (itr != sync_objects.end()) { if (itr != sync_objects.end()) {
// We found a ready object, acquire it and set the result value // We found a ready object, acquire it and set the result value
SynchronizationObject* object = itr->get(); SynchronizationObject* object = itr->get();
object->Acquire(thread); object->Acquire(thread);
const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr)); const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
return {RESULT_SUCCESS, index}; lock.CancelSleep();
return {RESULT_SUCCESS, index};
}
if (nano_seconds == 0) {
lock.CancelSleep();
return {RESULT_TIMEOUT, InvalidHandle};
}
if (thread->IsPendingTermination()) {
lock.CancelSleep();
return {ERR_THREAD_TERMINATING, InvalidHandle};
}
if (thread->IsSyncCancelled()) {
thread->SetSyncCancelled(false);
lock.CancelSleep();
return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle};
}
for (auto& object : sync_objects) {
object->AddWaitingThread(SharedFrom(thread));
}
thread->SetSynchronizationObjects(&sync_objects);
thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
thread->SetStatus(ThreadStatus::WaitSynch);
thread->SetWaitingSync(true);
}
thread->SetWaitingSync(false);
if (event_handle != InvalidHandle) {
auto& time_manager = kernel.TimeManager();
time_manager.UnscheduleTimeEvent(event_handle);
} }
// No objects were ready to be acquired, prepare to suspend the thread. {
SchedulerLock lock(kernel);
// If a timeout value of 0 was provided, just return the Timeout error code instead of ResultCode signaling_result = thread->GetSignalingResult();
// suspending the thread. SynchronizationObject* signaling_object = thread->GetSignalingObject();
if (nano_seconds == 0) { thread->SetSynchronizationObjects(nullptr);
return {RESULT_TIMEOUT, InvalidHandle}; auto shared_thread = SharedFrom(thread);
for (auto& obj : sync_objects) {
obj->RemoveWaitingThread(shared_thread);
}
if (signaling_object != nullptr) {
const auto itr = std::find_if(
sync_objects.begin(), sync_objects.end(),
[signaling_object](const std::shared_ptr<SynchronizationObject>& object) {
return object.get() == signaling_object;
});
ASSERT(itr != sync_objects.end());
signaling_object->Acquire(thread);
const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
return {signaling_result, index};
}
return {signaling_result, -1};
} }
if (thread->IsSyncCancelled()) {
thread->SetSyncCancelled(false);
return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle};
}
for (auto& object : sync_objects) {
object->AddWaitingThread(SharedFrom(thread));
}
thread->SetSynchronizationObjects(std::move(sync_objects));
thread->SetStatus(ThreadStatus::WaitSynch);
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
system.PrepareReschedule(thread->GetProcessorID());
return {RESULT_TIMEOUT, InvalidHandle};
} }
} // namespace Kernel } // namespace Kernel

@ -38,68 +38,8 @@ void SynchronizationObject::RemoveWaitingThread(std::shared_ptr<Thread> thread)
waiting_threads.erase(itr); waiting_threads.erase(itr);
} }
std::shared_ptr<Thread> SynchronizationObject::GetHighestPriorityReadyThread() const { void SynchronizationObject::ClearWaitingThreads() {
Thread* candidate = nullptr; waiting_threads.clear();
u32 candidate_priority = THREADPRIO_LOWEST + 1;
for (const auto& thread : waiting_threads) {
const ThreadStatus thread_status = thread->GetStatus();
// The list of waiting threads must not contain threads that are not waiting to be awakened.
ASSERT_MSG(thread_status == ThreadStatus::WaitSynch ||
thread_status == ThreadStatus::WaitHLEEvent,
"Inconsistent thread statuses in waiting_threads");
if (thread->GetPriority() >= candidate_priority)
continue;
if (ShouldWait(thread.get()))
continue;
candidate = thread.get();
candidate_priority = thread->GetPriority();
}
return SharedFrom(candidate);
}
void SynchronizationObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
ASSERT(!ShouldWait(thread.get()));
if (!thread) {
return;
}
if (thread->IsSleepingOnWait()) {
for (const auto& object : thread->GetSynchronizationObjects()) {
ASSERT(!object->ShouldWait(thread.get()));
object->Acquire(thread.get());
}
} else {
Acquire(thread.get());
}
const std::size_t index = thread->GetSynchronizationObjectIndex(SharedFrom(this));
thread->ClearSynchronizationObjects();
thread->CancelWakeupTimer();
bool resume = true;
if (thread->HasWakeupCallback()) {
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, SharedFrom(this),
index);
}
if (resume) {
thread->ResumeFromWait();
kernel.PrepareReschedule(thread->GetProcessorID());
}
}
void SynchronizationObject::WakeupAllWaitingThreads() {
while (auto thread = GetHighestPriorityReadyThread()) {
WakeupWaitingThread(thread);
}
} }
const std::vector<std::shared_ptr<Thread>>& SynchronizationObject::GetWaitingThreads() const { const std::vector<std::shared_ptr<Thread>>& SynchronizationObject::GetWaitingThreads() const {

@ -12,6 +12,7 @@
namespace Kernel { namespace Kernel {
class KernelCore; class KernelCore;
class Synchronization;
class Thread; class Thread;
/// Class that represents a Kernel object that a thread can be waiting on /// Class that represents a Kernel object that a thread can be waiting on
@ -49,24 +50,11 @@ public:
*/ */
void RemoveWaitingThread(std::shared_ptr<Thread> thread); void RemoveWaitingThread(std::shared_ptr<Thread> thread);
/**
* Wake up all threads waiting on this object that can be awoken, in priority order,
* and set the synchronization result and output of the thread.
*/
void WakeupAllWaitingThreads();
/**
* Wakes up a single thread waiting on this object.
* @param thread Thread that is waiting on this object to wakeup.
*/
void WakeupWaitingThread(std::shared_ptr<Thread> thread);
/// Obtains the highest priority thread that is ready to run from this object's waiting list.
std::shared_ptr<Thread> GetHighestPriorityReadyThread() const;
/// Get a const reference to the waiting threads list for debug use /// Get a const reference to the waiting threads list for debug use
const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const; const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const;
void ClearWaitingThreads();
protected: protected:
bool is_signaled{}; // Tells if this sync object is signalled; bool is_signaled{}; // Tells if this sync object is signalled;

@ -9,12 +9,21 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/fiber.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/thread_queue_list.h" #include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#endif
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/cpu_manager.h"
#include "core/hardware_properties.h" #include "core/hardware_properties.h"
#include "core/hle/kernel/errors.h" #include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/handle_table.h"
@ -23,6 +32,7 @@
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/memory.h" #include "core/memory.h"
@ -44,46 +54,26 @@ Thread::Thread(KernelCore& kernel) : SynchronizationObject{kernel} {}
Thread::~Thread() = default; Thread::~Thread() = default;
void Thread::Stop() { void Thread::Stop() {
// Cancel any outstanding wakeup events for this thread {
Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), SchedulerLock lock(kernel);
global_handle); SetStatus(ThreadStatus::Dead);
kernel.GlobalHandleTable().Close(global_handle); Signal();
global_handle = 0; kernel.GlobalHandleTable().Close(global_handle);
SetStatus(ThreadStatus::Dead);
Signal();
// Clean up any dangling references in objects that this thread was waiting for if (owner_process) {
for (auto& wait_object : wait_objects) { owner_process->UnregisterThread(this);
wait_object->RemoveWaitingThread(SharedFrom(this));
// Mark the TLS slot in the thread's page as free.
owner_process->FreeTLSRegion(tls_address);
}
arm_interface.reset();
has_exited = true;
} }
wait_objects.clear(); global_handle = 0;
owner_process->UnregisterThread(this);
// Mark the TLS slot in the thread's page as free.
owner_process->FreeTLSRegion(tls_address);
}
void Thread::WakeAfterDelay(s64 nanoseconds) {
// Don't schedule a wakeup if the thread wants to wait forever
if (nanoseconds == -1)
return;
// This function might be called from any thread so we have to be cautious and use the
// thread-safe version of ScheduleEvent.
const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
Core::System::GetInstance().CoreTiming().ScheduleEvent(
cycles, kernel.ThreadWakeupCallbackEventType(), global_handle);
}
void Thread::CancelWakeupTimer() {
Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(),
global_handle);
} }
void Thread::ResumeFromWait() { void Thread::ResumeFromWait() {
ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); SchedulerLock lock(kernel);
switch (status) { switch (status) {
case ThreadStatus::Paused: case ThreadStatus::Paused:
case ThreadStatus::WaitSynch: case ThreadStatus::WaitSynch:
@ -99,7 +89,7 @@ void Thread::ResumeFromWait() {
case ThreadStatus::Ready: case ThreadStatus::Ready:
// The thread's wakeup callback must have already been cleared when the thread was first // The thread's wakeup callback must have already been cleared when the thread was first
// awoken. // awoken.
ASSERT(wakeup_callback == nullptr); ASSERT(hle_callback == nullptr);
// If the thread is waiting on multiple wait objects, it might be awoken more than once // If the thread is waiting on multiple wait objects, it might be awoken more than once
// before actually resuming. We can ignore subsequent wakeups if the thread status has // before actually resuming. We can ignore subsequent wakeups if the thread status has
// already been set to ThreadStatus::Ready. // already been set to ThreadStatus::Ready.
@ -115,24 +105,31 @@ void Thread::ResumeFromWait() {
return; return;
} }
wakeup_callback = nullptr; SetStatus(ThreadStatus::Ready);
}
if (activity == ThreadActivity::Paused) { void Thread::OnWakeUp() {
SetStatus(ThreadStatus::Paused); SchedulerLock lock(kernel);
return;
}
SetStatus(ThreadStatus::Ready); SetStatus(ThreadStatus::Ready);
} }
ResultCode Thread::Start() {
SchedulerLock lock(kernel);
SetStatus(ThreadStatus::Ready);
return RESULT_SUCCESS;
}
void Thread::CancelWait() { void Thread::CancelWait() {
if (GetSchedulingStatus() != ThreadSchedStatus::Paused) { SchedulerLock lock(kernel);
if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) {
is_sync_cancelled = true; is_sync_cancelled = true;
return; return;
} }
// TODO(Blinkhawk): Implement cancel of server session
is_sync_cancelled = false; is_sync_cancelled = false;
SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED); SetSynchronizationResults(nullptr, ERR_SYNCHRONIZATION_CANCELED);
ResumeFromWait(); SetStatus(ThreadStatus::Ready);
} }
static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top, static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top,
@ -153,12 +150,29 @@ static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context,
context.fpcr = 0; context.fpcr = 0;
} }
ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::string name, std::shared_ptr<Common::Fiber>& Thread::GetHostContext() {
VAddr entry_point, u32 priority, u64 arg, return host_context;
s32 processor_id, VAddr stack_top, }
Process& owner_process) {
ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags,
std::string name, VAddr entry_point, u32 priority,
u64 arg, s32 processor_id, VAddr stack_top,
Process* owner_process) {
std::function<void(void*)> init_func = system.GetCpuManager().GetGuestThreadStartFunc();
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top,
owner_process, std::move(init_func), init_func_parameter);
}
ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags,
std::string name, VAddr entry_point, u32 priority,
u64 arg, s32 processor_id, VAddr stack_top,
Process* owner_process,
std::function<void(void*)>&& thread_start_func,
void* thread_start_parameter) {
auto& kernel = system.Kernel();
// Check if priority is in ranged. Lowest priority -> highest priority id. // Check if priority is in ranged. Lowest priority -> highest priority id.
if (priority > THREADPRIO_LOWEST) { if (priority > THREADPRIO_LOWEST && ((type_flags & THREADTYPE_IDLE) == 0)) {
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
return ERR_INVALID_THREAD_PRIORITY; return ERR_INVALID_THREAD_PRIORITY;
} }
@ -168,11 +182,12 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::strin
return ERR_INVALID_PROCESSOR_ID; return ERR_INVALID_PROCESSOR_ID;
} }
auto& system = Core::System::GetInstance(); if (owner_process) {
if (!system.Memory().IsValidVirtualAddress(owner_process, entry_point)) { if (!system.Memory().IsValidVirtualAddress(*owner_process, entry_point)) {
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
// TODO (bunnei): Find the correct error code to use here // TODO (bunnei): Find the correct error code to use here
return RESULT_UNKNOWN; return RESULT_UNKNOWN;
}
} }
std::shared_ptr<Thread> thread = std::make_shared<Thread>(kernel); std::shared_ptr<Thread> thread = std::make_shared<Thread>(kernel);
@ -183,51 +198,82 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::strin
thread->stack_top = stack_top; thread->stack_top = stack_top;
thread->tpidr_el0 = 0; thread->tpidr_el0 = 0;
thread->nominal_priority = thread->current_priority = priority; thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = system.CoreTiming().GetTicks(); thread->last_running_ticks = 0;
thread->processor_id = processor_id; thread->processor_id = processor_id;
thread->ideal_core = processor_id; thread->ideal_core = processor_id;
thread->affinity_mask = 1ULL << processor_id; thread->affinity_mask = 1ULL << processor_id;
thread->wait_objects.clear(); thread->wait_objects = nullptr;
thread->mutex_wait_address = 0; thread->mutex_wait_address = 0;
thread->condvar_wait_address = 0; thread->condvar_wait_address = 0;
thread->wait_handle = 0; thread->wait_handle = 0;
thread->name = std::move(name); thread->name = std::move(name);
thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap(); thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap();
thread->owner_process = &owner_process; thread->owner_process = owner_process;
auto& scheduler = kernel.GlobalScheduler(); thread->type = type_flags;
scheduler.AddThread(thread); if ((type_flags & THREADTYPE_IDLE) == 0) {
thread->tls_address = thread->owner_process->CreateTLSRegion(); auto& scheduler = kernel.GlobalScheduler();
scheduler.AddThread(thread);
}
if (owner_process) {
thread->tls_address = thread->owner_process->CreateTLSRegion();
thread->owner_process->RegisterThread(thread.get());
} else {
thread->tls_address = 0;
}
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
// to initialize the context
thread->arm_interface.reset();
if ((type_flags & THREADTYPE_HLE) == 0) {
#ifdef ARCHITECTURE_x86_64
if (owner_process && !owner_process->Is64BitProcess()) {
thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(),
processor_id);
} else {
thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(),
processor_id);
}
thread->owner_process->RegisterThread(thread.get()); #else
if (owner_process && !owner_process->Is64BitProcess()) {
ResetThreadContext32(thread->context_32, static_cast<u32>(stack_top), thread->arm_interface = std::make_shared<Core::ARM_Unicorn>(
static_cast<u32>(entry_point), static_cast<u32>(arg)); system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch32,
ResetThreadContext64(thread->context_64, stack_top, entry_point, arg); processor_id);
} else {
thread->arm_interface = std::make_shared<Core::ARM_Unicorn>(
system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch64,
processor_id);
}
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
ResetThreadContext32(thread->context_32, static_cast<u32>(stack_top),
static_cast<u32>(entry_point), static_cast<u32>(arg));
ResetThreadContext64(thread->context_64, stack_top, entry_point, arg);
}
thread->host_context =
std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter);
return MakeResult<std::shared_ptr<Thread>>(std::move(thread)); return MakeResult<std::shared_ptr<Thread>>(std::move(thread));
} }
void Thread::SetPriority(u32 priority) { void Thread::SetPriority(u32 priority) {
SchedulerLock lock(kernel);
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
"Invalid priority value."); "Invalid priority value.");
nominal_priority = priority; nominal_priority = priority;
UpdatePriority(); UpdatePriority();
} }
void Thread::SetWaitSynchronizationResult(ResultCode result) { void Thread::SetSynchronizationResults(SynchronizationObject* object, ResultCode result) {
context_32.cpu_registers[0] = result.raw; signaling_object = object;
context_64.cpu_registers[0] = result.raw; signaling_result = result;
}
void Thread::SetWaitSynchronizationOutput(s32 output) {
context_32.cpu_registers[1] = output;
context_64.cpu_registers[1] = output;
} }
s32 Thread::GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const { s32 Thread::GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const {
ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); ASSERT_MSG(!wait_objects->empty(), "Thread is not waiting for anything");
const auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); const auto match = std::find(wait_objects->rbegin(), wait_objects->rend(), object);
return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1); return static_cast<s32>(std::distance(match, wait_objects->rend()) - 1);
} }
VAddr Thread::GetCommandBufferAddress() const { VAddr Thread::GetCommandBufferAddress() const {
@ -236,6 +282,14 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + command_header_offset; return GetTLSAddress() + command_header_offset;
} }
Core::ARM_Interface& Thread::ArmInterface() {
return *arm_interface;
}
const Core::ARM_Interface& Thread::ArmInterface() const {
return *arm_interface;
}
void Thread::SetStatus(ThreadStatus new_status) { void Thread::SetStatus(ThreadStatus new_status) {
if (new_status == status) { if (new_status == status) {
return; return;
@ -257,10 +311,6 @@ void Thread::SetStatus(ThreadStatus new_status) {
break; break;
} }
if (status == ThreadStatus::Running) {
last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
}
status = new_status; status = new_status;
} }
@ -341,75 +391,116 @@ void Thread::UpdatePriority() {
lock_owner->UpdatePriority(); lock_owner->UpdatePriority();
} }
void Thread::ChangeCore(u32 core, u64 mask) {
SetCoreAndAffinityMask(core, mask);
}
bool Thread::AllSynchronizationObjectsReady() const { bool Thread::AllSynchronizationObjectsReady() const {
return std::none_of(wait_objects.begin(), wait_objects.end(), return std::none_of(wait_objects->begin(), wait_objects->end(),
[this](const std::shared_ptr<SynchronizationObject>& object) { [this](const std::shared_ptr<SynchronizationObject>& object) {
return object->ShouldWait(this); return object->ShouldWait(this);
}); });
} }
bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread, bool Thread::InvokeHLECallback(std::shared_ptr<Thread> thread) {
std::shared_ptr<SynchronizationObject> object, ASSERT(hle_callback);
std::size_t index) { return hle_callback(std::move(thread));
ASSERT(wakeup_callback);
return wakeup_callback(reason, std::move(thread), std::move(object), index);
} }
void Thread::SetActivity(ThreadActivity value) { ResultCode Thread::SetActivity(ThreadActivity value) {
activity = value; SchedulerLock lock(kernel);
auto sched_status = GetSchedulingStatus();
if (sched_status != ThreadSchedStatus::Runnable && sched_status != ThreadSchedStatus::Paused) {
return ERR_INVALID_STATE;
}
if (IsPendingTermination()) {
return RESULT_SUCCESS;
}
if (value == ThreadActivity::Paused) { if (value == ThreadActivity::Paused) {
// Set status if not waiting if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) != 0) {
if (status == ThreadStatus::Ready || status == ThreadStatus::Running) { return ERR_INVALID_STATE;
SetStatus(ThreadStatus::Paused);
kernel.PrepareReschedule(processor_id);
} }
} else if (status == ThreadStatus::Paused) { AddSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag);
// Ready to reschedule } else {
ResumeFromWait(); if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) == 0) {
return ERR_INVALID_STATE;
}
RemoveSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag);
} }
return RESULT_SUCCESS;
} }
void Thread::Sleep(s64 nanoseconds) { ResultCode Thread::Sleep(s64 nanoseconds) {
// Sleep current thread and check for next thread to schedule Handle event_handle{};
SetStatus(ThreadStatus::WaitSleep); {
SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
SetStatus(ThreadStatus::WaitSleep);
}
// Create an event to wake the thread up after the specified nanosecond delay has passed if (event_handle != InvalidHandle) {
WakeAfterDelay(nanoseconds); auto& time_manager = kernel.TimeManager();
time_manager.UnscheduleTimeEvent(event_handle);
}
return RESULT_SUCCESS;
} }
bool Thread::YieldSimple() { std::pair<ResultCode, bool> Thread::YieldSimple() {
auto& scheduler = kernel.GlobalScheduler(); bool is_redundant = false;
return scheduler.YieldThread(this); {
SchedulerLock lock(kernel);
is_redundant = kernel.GlobalScheduler().YieldThread(this);
}
return {RESULT_SUCCESS, is_redundant};
} }
bool Thread::YieldAndBalanceLoad() { std::pair<ResultCode, bool> Thread::YieldAndBalanceLoad() {
auto& scheduler = kernel.GlobalScheduler(); bool is_redundant = false;
return scheduler.YieldThreadAndBalanceLoad(this); {
SchedulerLock lock(kernel);
is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this);
}
return {RESULT_SUCCESS, is_redundant};
} }
bool Thread::YieldAndWaitForLoadBalancing() { std::pair<ResultCode, bool> Thread::YieldAndWaitForLoadBalancing() {
auto& scheduler = kernel.GlobalScheduler(); bool is_redundant = false;
return scheduler.YieldThreadAndWaitForLoadBalancing(this); {
SchedulerLock lock(kernel);
is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this);
}
return {RESULT_SUCCESS, is_redundant};
}
void Thread::AddSchedulingFlag(ThreadSchedFlags flag) {
const u32 old_state = scheduling_state;
pausing_state |= static_cast<u32>(flag);
const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
scheduling_state = base_scheduling | pausing_state;
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
}
void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
const u32 old_state = scheduling_state;
pausing_state &= ~static_cast<u32>(flag);
const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
scheduling_state = base_scheduling | pausing_state;
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
} }
void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) { void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
const u32 old_flags = scheduling_state; const u32 old_state = scheduling_state;
scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) | scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
static_cast<u32>(new_status); static_cast<u32>(new_status);
AdjustSchedulingOnStatus(old_flags); kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
} }
void Thread::SetCurrentPriority(u32 new_priority) { void Thread::SetCurrentPriority(u32 new_priority) {
const u32 old_priority = std::exchange(current_priority, new_priority); const u32 old_priority = std::exchange(current_priority, new_priority);
AdjustSchedulingOnPriority(old_priority); kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority);
} }
ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) { ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
SchedulerLock lock(kernel);
const auto HighestSetCore = [](u64 mask, u32 max_cores) { const auto HighestSetCore = [](u64 mask, u32 max_cores) {
for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) { for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) {
if (((mask >> core) & 1) != 0) { if (((mask >> core) & 1) != 0) {
@ -443,111 +534,12 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
processor_id = ideal_core; processor_id = ideal_core;
} }
} }
AdjustSchedulingOnAffinity(old_affinity_mask, old_core); kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core);
} }
} }
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
void Thread::AdjustSchedulingOnStatus(u32 old_flags) {
if (old_flags == scheduling_state) {
return;
}
auto& scheduler = kernel.GlobalScheduler();
if (static_cast<ThreadSchedStatus>(old_flags & static_cast<u32>(ThreadSchedMasks::LowMask)) ==
ThreadSchedStatus::Runnable) {
// In this case the thread was running, now it's pausing/exitting
if (processor_id >= 0) {
scheduler.Unschedule(current_priority, static_cast<u32>(processor_id), this);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
scheduler.Unsuggest(current_priority, core, this);
}
}
} else if (GetSchedulingStatus() == ThreadSchedStatus::Runnable) {
// The thread is now set to running from being stopped
if (processor_id >= 0) {
scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
scheduler.Suggest(current_priority, core, this);
}
}
}
scheduler.SetReselectionPending();
}
void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) {
return;
}
auto& scheduler = kernel.GlobalScheduler();
if (processor_id >= 0) {
scheduler.Unschedule(old_priority, static_cast<u32>(processor_id), this);
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
scheduler.Unsuggest(old_priority, core, this);
}
}
// Add thread to the new priority queues.
Thread* current_thread = GetCurrentThread();
if (processor_id >= 0) {
if (current_thread == this) {
scheduler.SchedulePrepend(current_priority, static_cast<u32>(processor_id), this);
} else {
scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this);
}
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
scheduler.Suggest(current_priority, core, this);
}
}
scheduler.SetReselectionPending();
}
void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) {
auto& scheduler = kernel.GlobalScheduler();
if (GetSchedulingStatus() != ThreadSchedStatus::Runnable ||
current_priority >= THREADPRIO_COUNT) {
return;
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (((old_affinity_mask >> core) & 1) != 0) {
if (core == static_cast<u32>(old_core)) {
scheduler.Unschedule(current_priority, core, this);
} else {
scheduler.Unsuggest(current_priority, core, this);
}
}
}
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (((affinity_mask >> core) & 1) != 0) {
if (core == static_cast<u32>(processor_id)) {
scheduler.Schedule(current_priority, core, this);
} else {
scheduler.Suggest(current_priority, core, this);
}
}
}
scheduler.SetReselectionPending();
}
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
/** /**

@ -6,26 +6,47 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/spin_lock.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/hle/kernel/object.h" #include "core/hle/kernel/object.h"
#include "core/hle/kernel/synchronization_object.h" #include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h" #include "core/hle/result.h"
namespace Common {
class Fiber;
}
namespace Core {
class ARM_Interface;
class System;
} // namespace Core
namespace Kernel { namespace Kernel {
class GlobalScheduler;
class KernelCore; class KernelCore;
class Process; class Process;
class Scheduler; class Scheduler;
enum ThreadPriority : u32 { enum ThreadPriority : u32 {
THREADPRIO_HIGHEST = 0, ///< Highest thread priority THREADPRIO_HIGHEST = 0, ///< Highest thread priority
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps THREADPRIO_MAX_CORE_MIGRATION = 2, ///< Highest priority for a core migration
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
THREADPRIO_LOWEST = 63, ///< Lowest thread priority THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities. THREADPRIO_LOWEST = 63, ///< Lowest thread priority
THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
};
enum ThreadType : u32 {
THREADTYPE_USER = 0x1,
THREADTYPE_KERNEL = 0x2,
THREADTYPE_HLE = 0x4,
THREADTYPE_IDLE = 0x8,
THREADTYPE_SUSPEND = 0x10,
}; };
enum ThreadProcessorId : s32 { enum ThreadProcessorId : s32 {
@ -107,26 +128,45 @@ public:
using ThreadSynchronizationObjects = std::vector<std::shared_ptr<SynchronizationObject>>; using ThreadSynchronizationObjects = std::vector<std::shared_ptr<SynchronizationObject>>;
using WakeupCallback = using HLECallback = std::function<bool(std::shared_ptr<Thread> thread)>;
std::function<bool(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<SynchronizationObject> object, std::size_t index)>;
/** /**
* Creates and returns a new thread. The new thread is immediately scheduled * Creates and returns a new thread. The new thread is immediately scheduled
* @param kernel The kernel instance this thread will be created under. * @param system The instance of the whole system
* @param name The friendly name desired for the thread * @param name The friendly name desired for the thread
* @param entry_point The address at which the thread should start execution * @param entry_point The address at which the thread should start execution
* @param priority The thread's priority * @param priority The thread's priority
* @param arg User data to pass to the thread * @param arg User data to pass to the thread
* @param processor_id The ID(s) of the processors on which the thread is desired to be run * @param processor_id The ID(s) of the processors on which the thread is desired to be run
* @param stack_top The address of the thread's stack top * @param stack_top The address of the thread's stack top
* @param owner_process The parent process for the thread * @param owner_process The parent process for the thread, if null, it's a kernel thread
* @return A shared pointer to the newly created thread * @return A shared pointer to the newly created thread
*/ */
static ResultVal<std::shared_ptr<Thread>> Create(KernelCore& kernel, std::string name, static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags,
VAddr entry_point, u32 priority, u64 arg, std::string name, VAddr entry_point,
s32 processor_id, VAddr stack_top, u32 priority, u64 arg, s32 processor_id,
Process& owner_process); VAddr stack_top, Process* owner_process);
/**
* Creates and returns a new thread. The new thread is immediately scheduled
* @param system The instance of the whole system
* @param name The friendly name desired for the thread
* @param entry_point The address at which the thread should start execution
* @param priority The thread's priority
* @param arg User data to pass to the thread
* @param processor_id The ID(s) of the processors on which the thread is desired to be run
* @param stack_top The address of the thread's stack top
* @param owner_process The parent process for the thread, if null, it's a kernel thread
* @param thread_start_func The function where the host context will start.
* @param thread_start_parameter The parameter which will passed to host context on init
* @return A shared pointer to the newly created thread
*/
static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags,
std::string name, VAddr entry_point,
u32 priority, u64 arg, s32 processor_id,
VAddr stack_top, Process* owner_process,
std::function<void(void*)>&& thread_start_func,
void* thread_start_parameter);
std::string GetName() const override { std::string GetName() const override {
return name; return name;
@ -181,7 +221,7 @@ public:
void UpdatePriority(); void UpdatePriority();
/// Changes the core that the thread is running or scheduled to run on. /// Changes the core that the thread is running or scheduled to run on.
void ChangeCore(u32 core, u64 mask); ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
/** /**
* Gets the thread's thread ID * Gets the thread's thread ID
@ -194,6 +234,10 @@ public:
/// Resumes a thread from waiting /// Resumes a thread from waiting
void ResumeFromWait(); void ResumeFromWait();
void OnWakeUp();
ResultCode Start();
/// Cancels a waiting operation that this thread may or may not be within. /// Cancels a waiting operation that this thread may or may not be within.
/// ///
/// When the thread is within a waiting state, this will set the thread's /// When the thread is within a waiting state, this will set the thread's
@ -202,26 +246,19 @@ public:
/// ///
void CancelWait(); void CancelWait();
/** void SetSynchronizationResults(SynchronizationObject* object, ResultCode result);
* Schedules an event to wake up the specified thread after the specified delay
* @param nanoseconds The time this thread will be allowed to sleep for
*/
void WakeAfterDelay(s64 nanoseconds);
/// Cancel any outstanding wakeup events for this thread Core::ARM_Interface& ArmInterface();
void CancelWakeupTimer();
/** const Core::ARM_Interface& ArmInterface() const;
* Sets the result after the thread awakens (from svcWaitSynchronization)
* @param result Value to set to the returned result
*/
void SetWaitSynchronizationResult(ResultCode result);
/** SynchronizationObject* GetSignalingObject() const {
* Sets the output parameter value after the thread awakens (from svcWaitSynchronization) return signaling_object;
* @param output Value to set to the output parameter }
*/
void SetWaitSynchronizationOutput(s32 output); ResultCode GetSignalingResult() const {
return signaling_result;
}
/** /**
* Retrieves the index that this particular object occupies in the list of objects * Retrieves the index that this particular object occupies in the list of objects
@ -269,11 +306,6 @@ public:
*/ */
VAddr GetCommandBufferAddress() const; VAddr GetCommandBufferAddress() const;
/// Returns whether this thread is waiting on objects from a WaitSynchronization call.
bool IsSleepingOnWait() const {
return status == ThreadStatus::WaitSynch;
}
ThreadContext32& GetContext32() { ThreadContext32& GetContext32() {
return context_32; return context_32;
} }
@ -290,6 +322,28 @@ public:
return context_64; return context_64;
} }
bool IsHLEThread() const {
return (type & THREADTYPE_HLE) != 0;
}
bool IsSuspendThread() const {
return (type & THREADTYPE_SUSPEND) != 0;
}
bool IsIdleThread() const {
return (type & THREADTYPE_IDLE) != 0;
}
bool WasRunning() const {
return was_running;
}
void SetWasRunning(bool value) {
was_running = value;
}
std::shared_ptr<Common::Fiber>& GetHostContext();
ThreadStatus GetStatus() const { ThreadStatus GetStatus() const {
return status; return status;
} }
@ -325,18 +379,18 @@ public:
} }
const ThreadSynchronizationObjects& GetSynchronizationObjects() const { const ThreadSynchronizationObjects& GetSynchronizationObjects() const {
return wait_objects; return *wait_objects;
} }
void SetSynchronizationObjects(ThreadSynchronizationObjects objects) { void SetSynchronizationObjects(ThreadSynchronizationObjects* objects) {
wait_objects = std::move(objects); wait_objects = objects;
} }
void ClearSynchronizationObjects() { void ClearSynchronizationObjects() {
for (const auto& waiting_object : wait_objects) { for (const auto& waiting_object : *wait_objects) {
waiting_object->RemoveWaitingThread(SharedFrom(this)); waiting_object->RemoveWaitingThread(SharedFrom(this));
} }
wait_objects.clear(); wait_objects->clear();
} }
/// Determines whether all the objects this thread is waiting on are ready. /// Determines whether all the objects this thread is waiting on are ready.
@ -386,26 +440,35 @@ public:
arb_wait_address = address; arb_wait_address = address;
} }
bool HasWakeupCallback() const { bool HasHLECallback() const {
return wakeup_callback != nullptr; return hle_callback != nullptr;
} }
void SetWakeupCallback(WakeupCallback callback) { void SetHLECallback(HLECallback callback) {
wakeup_callback = std::move(callback); hle_callback = std::move(callback);
} }
void InvalidateWakeupCallback() { void SetHLETimeEvent(Handle time_event) {
SetWakeupCallback(nullptr); hle_time_event = time_event;
} }
/** void SetHLESyncObject(SynchronizationObject* object) {
* Invokes the thread's wakeup callback. hle_object = object;
* }
* @pre A valid wakeup callback has been set. Violating this precondition
* will cause an assertion to trigger. Handle GetHLETimeEvent() const {
*/ return hle_time_event;
bool InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread, }
std::shared_ptr<SynchronizationObject> object, std::size_t index);
SynchronizationObject* GetHLESyncObject() const {
return hle_object;
}
void InvalidateHLECallback() {
SetHLECallback(nullptr);
}
bool InvokeHLECallback(std::shared_ptr<Thread> thread);
u32 GetIdealCore() const { u32 GetIdealCore() const {
return ideal_core; return ideal_core;
@ -415,23 +478,19 @@ public:
return affinity_mask; return affinity_mask;
} }
ThreadActivity GetActivity() const { ResultCode SetActivity(ThreadActivity value);
return activity;
}
void SetActivity(ThreadActivity value);
/// Sleeps this thread for the given amount of nanoseconds. /// Sleeps this thread for the given amount of nanoseconds.
void Sleep(s64 nanoseconds); ResultCode Sleep(s64 nanoseconds);
/// Yields this thread without rebalancing loads. /// Yields this thread without rebalancing loads.
bool YieldSimple(); std::pair<ResultCode, bool> YieldSimple();
/// Yields this thread and does a load rebalancing. /// Yields this thread and does a load rebalancing.
bool YieldAndBalanceLoad(); std::pair<ResultCode, bool> YieldAndBalanceLoad();
/// Yields this thread and if the core is left idle, loads are rebalanced /// Yields this thread and if the core is left idle, loads are rebalanced
bool YieldAndWaitForLoadBalancing(); std::pair<ResultCode, bool> YieldAndWaitForLoadBalancing();
void IncrementYieldCount() { void IncrementYieldCount() {
yield_count++; yield_count++;
@ -446,6 +505,10 @@ public:
static_cast<u32>(ThreadSchedMasks::LowMask)); static_cast<u32>(ThreadSchedMasks::LowMask));
} }
bool IsRunnable() const {
return scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable);
}
bool IsRunning() const { bool IsRunning() const {
return is_running; return is_running;
} }
@ -466,17 +529,67 @@ public:
return global_handle; return global_handle;
} }
private: bool IsWaitingForArbitration() const {
void SetSchedulingStatus(ThreadSchedStatus new_status); return waiting_for_arbitration;
void SetCurrentPriority(u32 new_priority); }
ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
void WaitForArbitration(bool set) {
waiting_for_arbitration = set;
}
bool IsWaitingSync() const {
return is_waiting_on_sync;
}
void SetWaitingSync(bool is_waiting) {
is_waiting_on_sync = is_waiting;
}
bool IsPendingTermination() const {
return will_be_terminated || GetSchedulingStatus() == ThreadSchedStatus::Exited;
}
bool IsPaused() const {
return pausing_state != 0;
}
bool IsContinuousOnSVC() const {
return is_continuous_on_svc;
}
void SetContinuousOnSVC(bool is_continuous) {
is_continuous_on_svc = is_continuous;
}
bool IsPhantomMode() const {
return is_phantom_mode;
}
void SetPhantomMode(bool phantom) {
is_phantom_mode = phantom;
}
bool HasExited() const {
return has_exited;
}
private:
friend class GlobalScheduler;
friend class Scheduler;
void SetSchedulingStatus(ThreadSchedStatus new_status);
void AddSchedulingFlag(ThreadSchedFlags flag);
void RemoveSchedulingFlag(ThreadSchedFlags flag);
void SetCurrentPriority(u32 new_priority);
void AdjustSchedulingOnStatus(u32 old_flags);
void AdjustSchedulingOnPriority(u32 old_priority);
void AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core); void AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core);
Common::SpinLock context_guard{};
ThreadContext32 context_32{}; ThreadContext32 context_32{};
ThreadContext64 context_64{}; ThreadContext64 context_64{};
std::unique_ptr<Core::ARM_Interface> arm_interface{};
std::shared_ptr<Common::Fiber> host_context{};
u64 thread_id = 0; u64 thread_id = 0;
@ -485,6 +598,8 @@ private:
VAddr entry_point = 0; VAddr entry_point = 0;
VAddr stack_top = 0; VAddr stack_top = 0;
ThreadType type;
/// Nominal thread priority, as set by the emulated application. /// Nominal thread priority, as set by the emulated application.
/// The nominal priority is the thread priority without priority /// The nominal priority is the thread priority without priority
/// inheritance taken into account. /// inheritance taken into account.
@ -509,7 +624,10 @@ private:
/// Objects that the thread is waiting on, in the same order as they were /// Objects that the thread is waiting on, in the same order as they were
/// passed to WaitSynchronization. /// passed to WaitSynchronization.
ThreadSynchronizationObjects wait_objects; ThreadSynchronizationObjects* wait_objects;
SynchronizationObject* signaling_object;
ResultCode signaling_result{RESULT_SUCCESS};
/// List of threads that are waiting for a mutex that is held by this thread. /// List of threads that are waiting for a mutex that is held by this thread.
MutexWaitingThreads wait_mutex_threads; MutexWaitingThreads wait_mutex_threads;
@ -526,30 +644,39 @@ private:
/// If waiting for an AddressArbiter, this is the address being waited on. /// If waiting for an AddressArbiter, this is the address being waited on.
VAddr arb_wait_address{0}; VAddr arb_wait_address{0};
bool waiting_for_arbitration{};
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue. /// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle global_handle = 0; Handle global_handle = 0;
/// Callback that will be invoked when the thread is resumed from a waiting state. If the thread /// Callback for HLE Events
/// was waiting via WaitSynchronization then the object will be the last object that became HLECallback hle_callback;
/// available. In case of a timeout, the object will be nullptr. Handle hle_time_event;
WakeupCallback wakeup_callback; SynchronizationObject* hle_object;
Scheduler* scheduler = nullptr; Scheduler* scheduler = nullptr;
u32 ideal_core{0xFFFFFFFF}; u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1}; u64 affinity_mask{0x1};
ThreadActivity activity = ThreadActivity::Normal;
s32 ideal_core_override = -1; s32 ideal_core_override = -1;
u64 affinity_mask_override = 0x1; u64 affinity_mask_override = 0x1;
u32 affinity_override_count = 0; u32 affinity_override_count = 0;
u32 scheduling_state = 0; u32 scheduling_state = 0;
u32 pausing_state = 0;
bool is_running = false; bool is_running = false;
bool is_waiting_on_sync = false;
bool is_sync_cancelled = false; bool is_sync_cancelled = false;
bool is_continuous_on_svc = false;
bool will_be_terminated = false;
bool is_phantom_mode = false;
bool has_exited = false;
bool was_running = false;
std::string name; std::string name;
}; };

@ -8,30 +8,37 @@
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h" #include "core/hle/kernel/time_manager.h"
namespace Kernel { namespace Kernel {
TimeManager::TimeManager(Core::System& system) : system{system} { TimeManager::TimeManager(Core::System& system_) : system{system_} {
time_manager_event_type = Core::Timing::CreateEvent( time_manager_event_type = Core::Timing::CreateEvent(
"Kernel::TimeManagerCallback", [this](u64 thread_handle, [[maybe_unused]] s64 cycles_late) { "Kernel::TimeManagerCallback", [this](u64 thread_handle, [[maybe_unused]] s64 cycles_late) {
SchedulerLock lock(system.Kernel());
Handle proper_handle = static_cast<Handle>(thread_handle); Handle proper_handle = static_cast<Handle>(thread_handle);
if (cancelled_events[proper_handle]) {
return;
}
std::shared_ptr<Thread> thread = std::shared_ptr<Thread> thread =
this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
thread->ResumeFromWait(); thread->OnWakeUp();
}); });
} }
void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) { void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) {
event_handle = timetask->GetGlobalHandle();
if (nanoseconds > 0) { if (nanoseconds > 0) {
ASSERT(timetask); ASSERT(timetask);
event_handle = timetask->GetGlobalHandle(); ASSERT(timetask->GetStatus() != ThreadStatus::Ready);
const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds}); ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex);
system.CoreTiming().ScheduleEvent(cycles, time_manager_event_type, event_handle); system.CoreTiming().ScheduleEvent(nanoseconds, time_manager_event_type, event_handle);
} else { } else {
event_handle = InvalidHandle; event_handle = InvalidHandle;
} }
cancelled_events[event_handle] = false;
} }
void TimeManager::UnscheduleTimeEvent(Handle event_handle) { void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
@ -39,6 +46,12 @@ void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
return; return;
} }
system.CoreTiming().UnscheduleEvent(time_manager_event_type, event_handle); system.CoreTiming().UnscheduleEvent(time_manager_event_type, event_handle);
cancelled_events[event_handle] = true;
}
void TimeManager::CancelTimeEvent(Thread* time_task) {
Handle event_handle = time_task->GetGlobalHandle();
UnscheduleTimeEvent(event_handle);
} }
} // namespace Kernel } // namespace Kernel

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <unordered_map>
#include "core/hle/kernel/object.h" #include "core/hle/kernel/object.h"
@ -35,9 +36,12 @@ public:
/// Unschedule an existing time event /// Unschedule an existing time event
void UnscheduleTimeEvent(Handle event_handle); void UnscheduleTimeEvent(Handle event_handle);
void CancelTimeEvent(Thread* time_task);
private: private:
Core::System& system; Core::System& system;
std::shared_ptr<Core::Timing::EventType> time_manager_event_type; std::shared_ptr<Core::Timing::EventType> time_manager_event_type;
std::unordered_map<Handle, bool> cancelled_events;
}; };
} // namespace Kernel } // namespace Kernel

@ -44,6 +44,218 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
return static_cast<u32>(std::min(size, max_jpeg_image_size)); return static_cast<u32>(std::min(size, max_jpeg_image_size));
} }
class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> {
public:
explicit IManagerForSystemService(Common::UUID user_id)
: ServiceFramework("IManagerForSystemService") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CheckAvailability"},
{1, nullptr, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
{100, nullptr, "SetSystemProgramIdentification"},
{101, nullptr, "RefreshNotificationTokenAsync"}, // 7.0.0+
{110, nullptr, "GetServiceEntryRequirementCache"}, // 4.0.0+
{111, nullptr, "InvalidateServiceEntryRequirementCache"}, // 4.0.0+
{112, nullptr, "InvalidateTokenCache"}, // 4.0.0 - 6.2.0
{113, nullptr, "GetServiceEntryRequirementCacheForOnlinePlay"}, // 6.1.0+
{120, nullptr, "GetNintendoAccountId"},
{121, nullptr, "CalculateNintendoAccountAuthenticationFingerprint"}, // 9.0.0+
{130, nullptr, "GetNintendoAccountUserResourceCache"},
{131, nullptr, "RefreshNintendoAccountUserResourceCacheAsync"},
{132, nullptr, "RefreshNintendoAccountUserResourceCacheAsyncIfSecondsElapsed"},
{133, nullptr, "GetNintendoAccountVerificationUrlCache"}, // 9.0.0+
{134, nullptr, "RefreshNintendoAccountVerificationUrlCache"}, // 9.0.0+
{135, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsyncIfSecondsElapsed"}, // 9.0.0+
{140, nullptr, "GetNetworkServiceLicenseCache"}, // 5.0.0+
{141, nullptr, "RefreshNetworkServiceLicenseCacheAsync"}, // 5.0.0+
{142, nullptr, "RefreshNetworkServiceLicenseCacheAsyncIfSecondsElapsed"}, // 5.0.0+
{150, nullptr, "CreateAuthorizationRequest"},
};
// clang-format on
RegisterHandlers(functions);
}
};
// 3.0.0+
class IFloatingRegistrationRequest final : public ServiceFramework<IFloatingRegistrationRequest> {
public:
explicit IFloatingRegistrationRequest(Common::UUID user_id)
: ServiceFramework("IFloatingRegistrationRequest") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
{12, nullptr, "GetAccountId"},
{13, nullptr, "GetLinkedNintendoAccountId"},
{14, nullptr, "GetNickname"},
{15, nullptr, "GetProfileImage"},
{21, nullptr, "LoadIdTokenCache"},
{100, nullptr, "RegisterUser"}, // [1.0.0-3.0.2] RegisterAsync
{101, nullptr, "RegisterUserWithUid"}, // [1.0.0-3.0.2] RegisterWithUidAsync
{102, nullptr, "RegisterNetworkServiceAccountAsync"}, // 4.0.0+
{103, nullptr, "RegisterNetworkServiceAccountWithUidAsync"}, // 4.0.0+
{110, nullptr, "SetSystemProgramIdentification"},
{111, nullptr, "EnsureIdTokenCacheAsync"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IAdministrator final : public ServiceFramework<IAdministrator> {
public:
explicit IAdministrator(Common::UUID user_id) : ServiceFramework("IAdministrator") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CheckAvailability"},
{1, nullptr, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
{100, nullptr, "SetSystemProgramIdentification"},
{101, nullptr, "RefreshNotificationTokenAsync"}, // 7.0.0+
{110, nullptr, "GetServiceEntryRequirementCache"}, // 4.0.0+
{111, nullptr, "InvalidateServiceEntryRequirementCache"}, // 4.0.0+
{112, nullptr, "InvalidateTokenCache"}, // 4.0.0 - 6.2.0
{113, nullptr, "GetServiceEntryRequirementCacheForOnlinePlay"}, // 6.1.0+
{120, nullptr, "GetNintendoAccountId"},
{121, nullptr, "CalculateNintendoAccountAuthenticationFingerprint"}, // 9.0.0+
{130, nullptr, "GetNintendoAccountUserResourceCache"},
{131, nullptr, "RefreshNintendoAccountUserResourceCacheAsync"},
{132, nullptr, "RefreshNintendoAccountUserResourceCacheAsyncIfSecondsElapsed"},
{133, nullptr, "GetNintendoAccountVerificationUrlCache"}, // 9.0.0+
{134, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsync"}, // 9.0.0+
{135, nullptr, "RefreshNintendoAccountVerificationUrlCacheAsyncIfSecondsElapsed"}, // 9.0.0+
{140, nullptr, "GetNetworkServiceLicenseCache"}, // 5.0.0+
{141, nullptr, "RefreshNetworkServiceLicenseCacheAsync"}, // 5.0.0+
{142, nullptr, "RefreshNetworkServiceLicenseCacheAsyncIfSecondsElapsed"}, // 5.0.0+
{150, nullptr, "CreateAuthorizationRequest"},
{200, nullptr, "IsRegistered"},
{201, nullptr, "RegisterAsync"},
{202, nullptr, "UnregisterAsync"},
{203, nullptr, "DeleteRegistrationInfoLocally"},
{220, nullptr, "SynchronizeProfileAsync"},
{221, nullptr, "UploadProfileAsync"},
{222, nullptr, "SynchronizaProfileAsyncIfSecondsElapsed"},
{250, nullptr, "IsLinkedWithNintendoAccount"},
{251, nullptr, "CreateProcedureToLinkWithNintendoAccount"},
{252, nullptr, "ResumeProcedureToLinkWithNintendoAccount"},
{255, nullptr, "CreateProcedureToUpdateLinkageStateOfNintendoAccount"},
{256, nullptr, "ResumeProcedureToUpdateLinkageStateOfNintendoAccount"},
{260, nullptr, "CreateProcedureToLinkNnidWithNintendoAccount"}, // 3.0.0+
{261, nullptr, "ResumeProcedureToLinkNnidWithNintendoAccount"}, // 3.0.0+
{280, nullptr, "ProxyProcedureToAcquireApplicationAuthorizationForNintendoAccount"},
{290, nullptr, "GetRequestForNintendoAccountUserResourceView"}, // 8.0.0+
{300, nullptr, "TryRecoverNintendoAccountUserStateAsync"}, // 6.0.0+
{400, nullptr, "IsServiceEntryRequirementCacheRefreshRequiredForOnlinePlay"}, // 6.1.0+
{401, nullptr, "RefreshServiceEntryRequirementCacheForOnlinePlayAsync"}, // 6.1.0+
{900, nullptr, "GetAuthenticationInfoForWin"}, // 9.0.0+
{901, nullptr, "ImportAsyncForWin"}, // 9.0.0+
{997, nullptr, "DebugUnlinkNintendoAccountAsync"},
{998, nullptr, "DebugSetAvailabilityErrorDetail"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IAuthorizationRequest final : public ServiceFramework<IAuthorizationRequest> {
public:
explicit IAuthorizationRequest(Common::UUID user_id)
: ServiceFramework("IAuthorizationRequest") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
{10, nullptr, "InvokeWithoutInteractionAsync"},
{19, nullptr, "IsAuthorized"},
{20, nullptr, "GetAuthorizationCode"},
{21, nullptr, "GetIdToken"},
{22, nullptr, "GetState"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IOAuthProcedure final : public ServiceFramework<IOAuthProcedure> {
public:
explicit IOAuthProcedure(Common::UUID user_id) : ServiceFramework("IOAuthProcedure") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
};
// clang-format on
RegisterHandlers(functions);
}
};
// 3.0.0+
class IOAuthProcedureForExternalNsa final : public ServiceFramework<IOAuthProcedureForExternalNsa> {
public:
explicit IOAuthProcedureForExternalNsa(Common::UUID user_id)
: ServiceFramework("IOAuthProcedureForExternalNsa") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
{100, nullptr, "GetAccountId"},
{101, nullptr, "GetLinkedNintendoAccountId"},
{102, nullptr, "GetNickname"},
{103, nullptr, "GetProfileImage"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IOAuthProcedureForNintendoAccountLinkage final
: public ServiceFramework<IOAuthProcedureForNintendoAccountLinkage> {
public:
explicit IOAuthProcedureForNintendoAccountLinkage(Common::UUID user_id)
: ServiceFramework("IOAuthProcedureForNintendoAccountLinkage") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
{100, nullptr, "GetRequestWithTheme"},
{101, nullptr, "IsNetworkServiceAccountReplaced"},
{199, nullptr, "GetUrlForIntroductionOfExtraMembership"}, // 2.0.0 - 5.1.0
};
// clang-format on
RegisterHandlers(functions);
}
};
class INotifier final : public ServiceFramework<INotifier> {
public:
explicit INotifier(Common::UUID user_id) : ServiceFramework("INotifier") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IProfileCommon : public ServiceFramework<IProfileCommon> { class IProfileCommon : public ServiceFramework<IProfileCommon> {
public: public:
explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id, explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id,
@ -226,6 +438,54 @@ public:
: IProfileCommon("IProfileEditor", true, user_id, profile_manager) {} : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {}
}; };
class IAsyncContext final : public ServiceFramework<IAsyncContext> {
public:
explicit IAsyncContext(Common::UUID user_id) : ServiceFramework("IAsyncContext") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
{1, nullptr, "Cancel"},
{2, nullptr, "HasDone"},
{3, nullptr, "GetResult"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class ISessionObject final : public ServiceFramework<ISessionObject> {
public:
explicit ISessionObject(Common::UUID user_id) : ServiceFramework("ISessionObject") {
// clang-format off
static const FunctionInfo functions[] = {
{999, nullptr, "Dummy"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IGuestLoginRequest final : public ServiceFramework<IGuestLoginRequest> {
public:
explicit IGuestLoginRequest(Common::UUID) : ServiceFramework("IGuestLoginRequest") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
{11, nullptr, "Unknown"}, // 1.0.0 - 2.3.0 (the name is blank on Switchbrew)
{12, nullptr, "GetAccountId"},
{13, nullptr, "GetLinkedNintendoAccountId"},
{14, nullptr, "GetNickname"},
{15, nullptr, "GetProfileImage"},
{21, nullptr, "LoadIdTokenCache"}, // 3.0.0+
};
// clang-format on
RegisterHandlers(functions);
}
};
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public: public:
explicit IManagerForApplication(Common::UUID user_id) explicit IManagerForApplication(Common::UUID user_id)
@ -265,6 +525,87 @@ private:
Common::UUID user_id; Common::UUID user_id;
}; };
// 6.0.0+
class IAsyncNetworkServiceLicenseKindContext final
: public ServiceFramework<IAsyncNetworkServiceLicenseKindContext> {
public:
explicit IAsyncNetworkServiceLicenseKindContext(Common::UUID user_id)
: ServiceFramework("IAsyncNetworkServiceLicenseKindContext") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
{1, nullptr, "Cancel"},
{2, nullptr, "HasDone"},
{3, nullptr, "GetResult"},
{4, nullptr, "GetNetworkServiceLicenseKind"},
};
// clang-format on
RegisterHandlers(functions);
}
};
// 8.0.0+
class IOAuthProcedureForUserRegistration final
: public ServiceFramework<IOAuthProcedureForUserRegistration> {
public:
explicit IOAuthProcedureForUserRegistration(Common::UUID user_id)
: ServiceFramework("IOAuthProcedureForUserRegistration") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
{1, nullptr, "GetRequest"},
{2, nullptr, "ApplyResponse"},
{3, nullptr, "ApplyResponseAsync"},
{10, nullptr, "Suspend"},
{100, nullptr, "GetAccountId"},
{101, nullptr, "GetLinkedNintendoAccountId"},
{102, nullptr, "GetNickname"},
{103, nullptr, "GetProfileImage"},
{110, nullptr, "RegisterUserAsync"},
{111, nullptr, "GetUid"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class DAUTH_O final : public ServiceFramework<DAUTH_O> {
public:
explicit DAUTH_O(Common::UUID) : ServiceFramework("dauth:o") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, // [5.0.0-5.1.0] GeneratePostData
{1, nullptr, "LoadAuthenticationTokenCache"}, // 6.0.0+
{2, nullptr, "InvalidateAuthenticationTokenCache"}, // 6.0.0+
{10, nullptr, "EnsureEdgeTokenCacheAsync"}, // 6.0.0+
{11, nullptr, "LoadEdgeTokenCache"}, // 6.0.0+
{12, nullptr, "InvalidateEdgeTokenCache"}, // 6.0.0+
};
// clang-format on
RegisterHandlers(functions);
}
};
// 6.0.0+
class IAsyncResult final : public ServiceFramework<IAsyncResult> {
public:
explicit IAsyncResult(Common::UUID user_id) : ServiceFramework("IAsyncResult") {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetResult"},
{1, nullptr, "Cancel"},
{2, nullptr, "IsAvailable"},
{3, nullptr, "GetSystemEvent"},
};
// clang-format on
RegisterHandlers(functions);
}
};
void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called"); LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
@ -435,6 +776,15 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
// TODO(ogniK): Handle open contexts
ctx.WriteBuffer(profile_manager->GetOpenUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called"); LOG_DEBUG(Service_ACC, "called");
// A u8 is passed into this function which we can safely ignore. It's to determine if we have // A u8 is passed into this function which we can safely ignore. It's to determine if we have

@ -34,6 +34,7 @@ public:
void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx); void GetProfileEditor(Kernel::HLERequestContext& ctx);
void ListQualifiedUsers(Kernel::HLERequestContext& ctx); void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
private: private:
ResultCode InitializeApplicationInfoBase(); ResultCode InitializeApplicationInfoBase();

@ -13,8 +13,8 @@ ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{0, nullptr, "EnsureCacheAsync"}, {0, nullptr, "EnsureCacheAsync"},
{1, nullptr, "LoadCache"}, {1, nullptr, "LoadCache"},
{2, nullptr, "GetDeviceAccountId"}, {2, nullptr, "GetDeviceAccountId"},
{50, nullptr, "RegisterNotificationTokenAsync"}, {50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
{51, nullptr, "UnregisterNotificationTokenAsync"}, {51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
}; };
RegisterHandlers(functions); RegisterHandlers(functions);
} }

@ -17,28 +17,28 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{3, &ACC_SU::ListOpenUsers, "ListOpenUsers"}, {3, &ACC_SU::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_SU::GetProfile, "GetProfile"}, {5, &ACC_SU::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, {6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, {51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"}, {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"}, {100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"}, {101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"}, {102, nullptr, "GetBaasAccountManagerForSystemService"},
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
{104, nullptr, "GetProfileUpdateNotifier"}, {104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{106, nullptr, "GetProfileSyncNotifier"}, {106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
{110, nullptr, "StoreSaveDataThumbnail"}, {110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"}, {111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"}, {112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"}, {113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
{120, nullptr, "ListOpenUsersInApplication"}, {120, nullptr, "ListOpenUsersInApplication"}, // 10.0.0+
{130, nullptr, "ActivateOpenContextRetention"}, {130, nullptr, "ActivateOpenContextRetention"}, // 6.0.0+
{140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"}, {140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{150, nullptr, "AuthenticateApplicationAsync"}, {150, nullptr, "AuthenticateApplicationAsync"}, // 10.0.0+
{190, nullptr, "GetUserLastOpenedApplication"}, {190, nullptr, "GetUserLastOpenedApplication"}, // 1.0.0 - 9.2.0
{191, nullptr, "ActivateOpenContextHolder"}, {191, nullptr, "ActivateOpenContextHolder"}, // 7.0.0+
{200, nullptr, "BeginUserRegistration"}, {200, nullptr, "BeginUserRegistration"},
{201, nullptr, "CompleteUserRegistration"}, {201, nullptr, "CompleteUserRegistration"},
{202, nullptr, "CancelUserRegistration"}, {202, nullptr, "CancelUserRegistration"},
@ -46,15 +46,15 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{204, nullptr, "SetUserPosition"}, {204, nullptr, "SetUserPosition"},
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"}, {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
{206, nullptr, "CompleteUserRegistrationForcibly"}, {206, nullptr, "CompleteUserRegistrationForcibly"},
{210, nullptr, "CreateFloatingRegistrationRequest"}, {210, nullptr, "CreateFloatingRegistrationRequest"}, // 3.0.0+
{211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, {211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, // 8.0.0+
{212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, {212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, // 8.0.0+
{230, nullptr, "AuthenticateServiceAsync"}, {230, nullptr, "AuthenticateServiceAsync"},
{250, nullptr, "GetBaasAccountAdministrator"}, {250, nullptr, "GetBaasAccountAdministrator"},
{290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"}, {290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
{291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, // 3.0.0+
{299, nullptr, "SuspendBackgroundDaemon"}, {299, nullptr, "SuspendBackgroundDaemon"},
{997, nullptr, "DebugInvalidateTokenCacheForUser"}, {997, nullptr, "DebugInvalidateTokenCacheForUser"}, // 3.0.0+
{998, nullptr, "DebugSetUserStateClose"}, {998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"}, {999, nullptr, "DebugSetUserStateOpen"},
}; };

@ -17,23 +17,23 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{3, &ACC_U0::ListOpenUsers, "ListOpenUsers"}, {3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U0::GetProfile, "GetProfile"}, {5, &ACC_U0::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, {6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, {51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"}, {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
{102, nullptr, "AuthenticateApplicationAsync"}, {102, nullptr, "AuthenticateApplicationAsync"},
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{110, nullptr, "StoreSaveDataThumbnail"}, {110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"}, {111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"}, {120, nullptr, "CreateGuestLoginRequest"},
{130, nullptr, "LoadOpenContext"}, {130, nullptr, "LoadOpenContext"}, // 5.0.0+
{131, nullptr, "ListOpenContextStoredUsers"}, {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+
}; };
// clang-format on // clang-format on

@ -17,28 +17,29 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{3, &ACC_U1::ListOpenUsers, "ListOpenUsers"}, {3, &ACC_U1::ListOpenUsers, "ListOpenUsers"},
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
{5, &ACC_U1::GetProfile, "GetProfile"}, {5, &ACC_U1::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, {6, nullptr, "GetProfileDigest"}, // 3.0.0+
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, {51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, nullptr, "ListOpenContextStoredUsers"}, {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
{99, nullptr, "DebugActivateOpenContextRetention"}, {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
{100, nullptr, "GetUserRegistrationNotifier"}, {100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"}, {101, nullptr, "GetUserStateChangeNotifier"},
{102, nullptr, "GetBaasAccountManagerForSystemService"}, {102, nullptr, "GetBaasAccountManagerForSystemService"},
{103, nullptr, "GetProfileUpdateNotifier"}, {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
{104, nullptr, "CheckNetworkServiceAvailabilityAsync"}, {104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{106, nullptr, "GetProfileSyncNotifier"}, {106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
{110, nullptr, "StoreSaveDataThumbnail"}, {110, nullptr, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"}, {111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"}, {112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"}, {113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
{130, nullptr, "ActivateOpenContextRetention"}, {120, nullptr, "ListOpenUsersInApplication"}, // 10.0.0+
{140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"}, {130, nullptr, "ActivateOpenContextRetention"}, // 6.0.0+
{150, nullptr, "AuthenticateApplicationAsync"}, {140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
{190, nullptr, "GetUserLastOpenedApplication"}, {150, nullptr, "AuthenticateApplicationAsync"}, // 10.0.0+
{191, nullptr, "ActivateOpenContextHolder"}, {190, nullptr, "GetUserLastOpenedApplication"}, // 1.0.0 - 9.2.0
{997, nullptr, "DebugInvalidateTokenCacheForUser"}, {191, nullptr, "ActivateOpenContextHolder"}, // 7.0.0+
{997, nullptr, "DebugInvalidateTokenCacheForUser"}, // 3.0.0+
{998, nullptr, "DebugSetUserStateClose"}, {998, nullptr, "DebugSetUserStateClose"},
{999, nullptr, "DebugSetUserStateOpen"}, {999, nullptr, "DebugSetUserStateOpen"},
}; };

Some files were not shown because too many files have changed in this diff Show More