Compare commits
151 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4c0b7b437 | ||
|
|
c75a4bdeaa | ||
|
|
2f37c7948f | ||
|
|
f107e58fde | ||
|
|
c70e1d0247 | ||
|
|
4b773b15a6 | ||
|
|
9fe077635e | ||
|
|
5c7eef3756 | ||
|
|
ddf5577799 | ||
|
|
f706b3bd24 | ||
|
|
d574bb4610 | ||
|
|
b0ba1a0b65 | ||
|
|
0ba03d1b3a | ||
|
|
fcebd36cde | ||
|
|
ae6dd1143c | ||
|
|
1d38109714 | ||
|
|
6a9bbb0128 | ||
|
|
d2170075e6 | ||
|
|
553be194f6 | ||
|
|
048d3e2404 | ||
|
|
3c925a7282 | ||
|
|
e37d00332c | ||
|
|
d3114c620d | ||
|
|
26b76d2eaf | ||
|
|
e2164f3417 | ||
|
|
c0fb5e876d | ||
|
|
a9ace6856d | ||
|
|
64c2ccb0cb | ||
|
|
dbacb31f61 | ||
|
|
0b9f2c2f14 | ||
|
|
e158167139 | ||
|
|
3da4280e81 | ||
|
|
77177a7e33 | ||
|
|
61a8696510 | ||
|
|
9b34afa588 | ||
|
|
6bcd676b61 | ||
|
|
133a68ee9b | ||
|
|
b1cd6cec19 | ||
|
|
d9336860d7 | ||
|
|
4496030ea9 | ||
|
|
eb74ef474b | ||
|
|
682c50715c | ||
|
|
c3cae9d992 | ||
|
|
224a19758e | ||
|
|
8c9e238a7b | ||
|
|
55e6d0dae0 | ||
|
|
9632434243 | ||
|
|
ec9550ced5 | ||
|
|
47a2efee73 | ||
|
|
5b7c0f13d3 | ||
|
|
ddf64e56af | ||
|
|
155213484b | ||
|
|
b7ad83383f | ||
|
|
6f101e0f02 | ||
|
|
972b93bf00 | ||
|
|
a5476541f2 | ||
|
|
1e35ade1ec | ||
|
|
b8777b6653 | ||
|
|
752659aef3 | ||
|
|
9574429c5f | ||
|
|
20cf09471a | ||
|
|
61883d8820 | ||
|
|
bb86fc573f | ||
|
|
1effa578f1 | ||
|
|
df6dffa30b | ||
|
|
aedd739631 | ||
|
|
ca3db0d7c9 | ||
|
|
0d99b7962d | ||
|
|
c80ed6d81f | ||
|
|
903705043d | ||
|
|
11e1cbbdbd | ||
|
|
fa342cae22 | ||
|
|
fedd983f96 | ||
|
|
d97d409647 | ||
|
|
c2b7de66b3 | ||
|
|
8a372035db | ||
|
|
9982cff98b | ||
|
|
fe24c65153 | ||
|
|
1a9b71b1c6 | ||
|
|
cdce7f781b | ||
|
|
8d774e7415 | ||
|
|
ada09778d9 | ||
|
|
8fd1d769fe | ||
|
|
afab6c143c | ||
|
|
fd7afda1e8 | ||
|
|
770e19f51a | ||
|
|
8bb604b3be | ||
|
|
a9ca39f859 | ||
|
|
b59ca4df0c | ||
|
|
98317f2b77 | ||
|
|
f5fd6b5c86 | ||
|
|
b2099fbdcc | ||
|
|
7cfa28a666 | ||
|
|
5a568b1655 | ||
|
|
3d02143476 | ||
|
|
ba34cf0a69 | ||
|
|
a283eda320 | ||
|
|
359f22b808 | ||
|
|
5caa150e9a | ||
|
|
bc8b3d225e | ||
|
|
4d60410dd9 | ||
|
|
98b5e236d4 | ||
|
|
920429fde7 | ||
|
|
2931101e6f | ||
|
|
668e80a9f4 | ||
|
|
e44ac8b821 | ||
|
|
f350c3d74e | ||
|
|
9cf4c8831d | ||
|
|
e462191482 | ||
|
|
bb74973bba | ||
|
|
6fc4012396 | ||
|
|
feb49c822d | ||
|
|
c6ea0c650e | ||
|
|
0f4ae3cc52 | ||
|
|
835b950f7e | ||
|
|
cbaf3fb433 | ||
|
|
b617874724 | ||
|
|
3f8e7a5585 | ||
|
|
d7990c159e | ||
|
|
2c62563ab5 | ||
|
|
139ea93512 | ||
|
|
c77b8df12e | ||
|
|
ad038609c8 | ||
|
|
68d9504a04 | ||
|
|
af35dbcf63 | ||
|
|
de0e8eff42 | ||
|
|
3cbe352c18 | ||
|
|
a21b8824fb | ||
|
|
d30b885d71 | ||
|
|
39a5ce4e69 | ||
|
|
ac104a24d1 | ||
|
|
7b7f6f1cb7 | ||
|
|
31d4bc6953 | ||
|
|
1a49991676 | ||
|
|
d55096ce85 | ||
|
|
1689530f52 | ||
|
|
3b5a937125 | ||
|
|
71fe9fd0f2 | ||
|
|
4774e32593 | ||
|
|
fc0ace6048 | ||
|
|
92c0ad23eb | ||
|
|
e85c19adcb | ||
|
|
35d3e7db2a | ||
|
|
ae7062d522 | ||
|
|
1225627515 | ||
|
|
1dba5fab62 | ||
|
|
b80f7faebe | ||
|
|
087c6c2ef1 | ||
|
|
db88eaa346 | ||
|
|
09a87966e0 | ||
|
|
bfb7cbc292 |
@@ -3,15 +3,6 @@
|
||||
# SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Setup RC file for tx
|
||||
cat << EOF > ~/.transifexrc
|
||||
[https://www.transifex.com]
|
||||
hostname = https://www.transifex.com
|
||||
username = api
|
||||
password = $TRANSIFEX_API_TOKEN
|
||||
EOF
|
||||
|
||||
|
||||
set -x
|
||||
|
||||
echo -e "\e[1m\e[33mBuild tools information:\e[0m"
|
||||
@@ -19,9 +10,6 @@ cmake --version
|
||||
gcc -v
|
||||
tx --version
|
||||
|
||||
# vcpkg needs these: curl zip unzip tar, have tar
|
||||
apt-get install -y curl zip unzip
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF -DYUZU_TESTS=OFF -DYUZU_USE_BUNDLED_VCPKG=ON
|
||||
make translation
|
||||
|
||||
@@ -10,13 +10,9 @@ set -e
|
||||
ccache -sv
|
||||
|
||||
mkdir -p build && cd build
|
||||
export LDFLAGS="-fuse-ld=lld"
|
||||
# -femulated-tls required due to an incompatibility between GCC and Clang
|
||||
# TODO(lat9nq): If this is widespread, we probably need to add this to CMakeLists where appropriate
|
||||
export CXXFLAGS="-femulated-tls"
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_TOOLCHAIN_FILE="${PWD}/../CMakeModules/MinGWClangCross.cmake" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="${PWD}/../CMakeModules/MinGWCross.cmake" \
|
||||
-DDISPLAY_VERSION="$1" \
|
||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -19,11 +19,11 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
fetch-depth: 0
|
||||
- name: Update Translation
|
||||
run: ./.ci/scripts/transifex/docker.sh
|
||||
env:
|
||||
TRANSIFEX_API_TOKEN: ${{ secrets.TRANSIFEX_API_TOKEN }}
|
||||
TX_TOKEN: ${{ secrets.TRANSIFEX_API_TOKEN }}
|
||||
|
||||
reuse:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -252,7 +252,7 @@ if(ENABLE_QT)
|
||||
endif()
|
||||
|
||||
# Check for headers
|
||||
Include(FindPkgConfig REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(QT_DEP_GLU QUIET glu>=9.0.0)
|
||||
if (NOT QT_DEP_GLU_FOUND)
|
||||
message(FATAL_ERROR "Qt bundled pacakge dependency `glu` not found. \
|
||||
@@ -386,7 +386,7 @@ endif()
|
||||
|
||||
# Ensure libusb is properly configured (based on dolphin libusb include)
|
||||
if(NOT APPLE AND NOT YUZU_USE_BUNDLED_LIBUSB)
|
||||
include(FindPkgConfig)
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND AND NOT CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD")
|
||||
pkg_check_modules(LIBUSB QUIET libusb-1.0>=1.0.24)
|
||||
else()
|
||||
@@ -410,7 +410,7 @@ set(FFmpeg_COMPONENTS
|
||||
swscale)
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
Include(FindPkgConfig REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBVA libva)
|
||||
endif()
|
||||
if (NOT YUZU_USE_BUNDLED_FFMPEG)
|
||||
|
||||
2
dist/languages/.tx/config
vendored
2
dist/languages/.tx/config
vendored
@@ -1,7 +1,7 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[yuzu.emulator]
|
||||
[o:yuzu-emulator:p:yuzu:r:emulator]
|
||||
file_filter = <lang>.ts
|
||||
source_file = en.ts
|
||||
source_lang = en
|
||||
|
||||
4
dist/languages/README.md
vendored
4
dist/languages/README.md
vendored
@@ -1 +1,3 @@
|
||||
This directory stores translation patches (TS files) for yuzu Qt frontend. This directory is linked with [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu), so you can update the translation by executing `tx pull -a`. If you want to contribute to the translation, please go the transifex link and submit your translation there. This directory on the main repo will be synchronized with transifex periodically. Do not directly open PRs on github to modify the translation.
|
||||
This directory stores translation patches (TS files) for yuzu Qt frontend. This directory is linked with [yuzu project on transifex](https://www.transifex.com/yuzu-emulator/yuzu), so you can update the translation by executing `tx pull -t -a`. If you want to contribute to the translation, please go the transifex link and submit your translation there. This directory on the main repo will be synchronized with transifex periodically.
|
||||
|
||||
Do not directly open PRs on github to modify the translation.
|
||||
|
||||
2
externals/ffmpeg/CMakeLists.txt
vendored
2
externals/ffmpeg/CMakeLists.txt
vendored
@@ -43,7 +43,7 @@ if (NOT WIN32)
|
||||
CACHE PATH "Paths to FFmpeg libraries" FORCE)
|
||||
endforeach()
|
||||
|
||||
Include(FindPkgConfig REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBVA libva)
|
||||
pkg_check_modules(CUDA cuda)
|
||||
pkg_check_modules(FFNVCODEC ffnvcodec)
|
||||
|
||||
2
externals/libusb/CMakeLists.txt
vendored
2
externals/libusb/CMakeLists.txt
vendored
@@ -108,7 +108,7 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
|
||||
target_include_directories(usb INTERFACE "${LIBUSB_INCLUDE_DIRS}")
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
Include(FindPkgConfig)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBUDEV REQUIRED libudev)
|
||||
|
||||
if (LIBUDEV_FOUND)
|
||||
|
||||
@@ -121,6 +121,7 @@ else()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
add_compile_options("-mcx16")
|
||||
add_compile_options("-fwrapv")
|
||||
endif()
|
||||
|
||||
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
|
||||
@@ -23,7 +23,7 @@ System::~System() {
|
||||
void System::Finalize() {
|
||||
Stop();
|
||||
session->Finalize();
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
buffer_event->Signal();
|
||||
}
|
||||
|
||||
void System::StartSession() {
|
||||
@@ -142,7 +142,7 @@ void System::ReleaseBuffers() {
|
||||
|
||||
if (signal) {
|
||||
// Signal if any buffer was released, or if none are registered, we need more.
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
buffer_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ bool System::FlushAudioInBuffers() {
|
||||
buffers.FlushBuffers(buffers_released);
|
||||
|
||||
if (buffers_released > 0) {
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
buffer_event->Signal();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ System::~System() {
|
||||
void System::Finalize() {
|
||||
Stop();
|
||||
session->Finalize();
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
buffer_event->Signal();
|
||||
}
|
||||
|
||||
std::string_view System::GetDefaultOutputDeviceName() const {
|
||||
@@ -141,7 +141,7 @@ void System::ReleaseBuffers() {
|
||||
bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
|
||||
if (signal) {
|
||||
// Signal if any buffer was released, or if none are registered, we need more.
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
buffer_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ bool System::FlushAudioOutBuffers() {
|
||||
buffers.FlushBuffers(buffers_released);
|
||||
|
||||
if (buffers_released > 0) {
|
||||
buffer_event->GetWritableEvent().Signal();
|
||||
buffer_event->Signal();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ void AudioRenderer::CreateSinkStreams() {
|
||||
}
|
||||
|
||||
void AudioRenderer::ThreadFunc() {
|
||||
constexpr char name[]{"yuzu:AudioRenderer"};
|
||||
constexpr char name[]{"AudioRenderer"};
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
|
||||
@@ -534,7 +534,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
|
||||
return result;
|
||||
}
|
||||
|
||||
adsp_rendered_event->GetWritableEvent().Clear();
|
||||
adsp_rendered_event->Clear();
|
||||
num_times_updated++;
|
||||
|
||||
const auto end_time{core.CoreTiming().GetClockTicks()};
|
||||
@@ -625,7 +625,7 @@ void System::SendCommandToDsp() {
|
||||
reset_command_buffers = false;
|
||||
command_buffer_size = command_size;
|
||||
if (remaining_command_count == 0) {
|
||||
adsp_rendered_event->GetWritableEvent().Signal();
|
||||
adsp_rendered_event->Signal();
|
||||
}
|
||||
} else {
|
||||
adsp.ClearRemainCount(session_id);
|
||||
|
||||
@@ -94,7 +94,7 @@ bool SystemManager::Remove(System& system_) {
|
||||
}
|
||||
|
||||
void SystemManager::ThreadFunc() {
|
||||
constexpr char name[]{"yuzu:AudioRenderSystemManager"};
|
||||
constexpr char name[]{"AudioRenderSystemManager"};
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
|
||||
@@ -66,10 +66,10 @@ public:
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
|
||||
minimum_latency = 256U;
|
||||
minimum_latency = TargetSampleCount * 2;
|
||||
}
|
||||
|
||||
minimum_latency = std::max(minimum_latency, 256u);
|
||||
minimum_latency = std::max(minimum_latency, TargetSampleCount * 2);
|
||||
|
||||
LOG_INFO(Service_Audio,
|
||||
"Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
|
||||
@@ -326,4 +326,31 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) {
|
||||
return device_list;
|
||||
}
|
||||
|
||||
u32 GetCubebLatency() {
|
||||
cubeb* ctx;
|
||||
|
||||
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
// Return a large latency so we choose SDL instead.
|
||||
return 10000u;
|
||||
}
|
||||
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = 2;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
|
||||
latency = TargetSampleCount * 2;
|
||||
}
|
||||
latency = std::max(latency, TargetSampleCount * 2);
|
||||
cubeb_destroy(ctx);
|
||||
return latency;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
|
||||
@@ -96,4 +96,11 @@ private:
|
||||
*/
|
||||
std::vector<std::string> ListCubebSinkDevices(bool capture);
|
||||
|
||||
/**
|
||||
* Get the reported latency for this sink.
|
||||
*
|
||||
* @return Minimum latency for this sink.
|
||||
*/
|
||||
u32 GetCubebLatency();
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
|
||||
@@ -47,11 +47,7 @@ public:
|
||||
spec.freq = TargetSampleRate;
|
||||
spec.channels = static_cast<u8>(device_channels);
|
||||
spec.format = AUDIO_S16SYS;
|
||||
if (type == StreamType::Render) {
|
||||
spec.samples = TargetSampleCount;
|
||||
} else {
|
||||
spec.samples = 1024;
|
||||
}
|
||||
spec.samples = TargetSampleCount * 2;
|
||||
spec.callback = &SDLSinkStream::DataCallback;
|
||||
spec.userdata = this;
|
||||
|
||||
@@ -234,10 +230,16 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
|
||||
|
||||
const int device_count = SDL_GetNumAudioDevices(capture);
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
|
||||
if (const char* name = SDL_GetAudioDeviceName(i, capture)) {
|
||||
device_list.emplace_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
u32 GetSDLLatency() {
|
||||
return TargetSampleCount * 2;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
|
||||
@@ -87,4 +87,11 @@ private:
|
||||
*/
|
||||
std::vector<std::string> ListSDLSinkDevices(bool capture);
|
||||
|
||||
/**
|
||||
* Get the reported latency for this sink.
|
||||
*
|
||||
* @return Minimum latency for this sink.
|
||||
*/
|
||||
u32 GetSDLLatency();
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
|
||||
@@ -21,58 +21,80 @@ namespace {
|
||||
struct SinkDetails {
|
||||
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
|
||||
using ListDevicesFn = std::vector<std::string> (*)(bool);
|
||||
using LatencyFn = u32 (*)();
|
||||
|
||||
/// Name for this sink.
|
||||
const char* id;
|
||||
std::string_view id;
|
||||
/// A method to call to construct an instance of this type of sink.
|
||||
FactoryFn factory;
|
||||
/// A method to call to list available devices.
|
||||
ListDevicesFn list_devices;
|
||||
/// Method to get the latency of this backend.
|
||||
LatencyFn latency;
|
||||
};
|
||||
|
||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr SinkDetails sink_details[] = {
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{"cubeb",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<CubebSink>(device_id);
|
||||
},
|
||||
&ListCubebSinkDevices},
|
||||
SinkDetails{
|
||||
"cubeb",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<CubebSink>(device_id);
|
||||
},
|
||||
&ListCubebSinkDevices,
|
||||
&GetCubebLatency,
|
||||
},
|
||||
#endif
|
||||
#ifdef HAVE_SDL2
|
||||
SinkDetails{"sdl2",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<SDLSink>(device_id);
|
||||
},
|
||||
&ListSDLSinkDevices},
|
||||
SinkDetails{
|
||||
"sdl2",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<SDLSink>(device_id);
|
||||
},
|
||||
&ListSDLSinkDevices,
|
||||
&GetSDLLatency,
|
||||
},
|
||||
#endif
|
||||
SinkDetails{"null",
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<NullSink>(device_id);
|
||||
},
|
||||
[](bool capture) { return std::vector<std::string>{"null"}; }},
|
||||
[](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }},
|
||||
};
|
||||
|
||||
const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
|
||||
auto iter =
|
||||
std::find_if(std::begin(sink_details), std::end(sink_details),
|
||||
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||
const auto find_backend{[](std::string_view id) {
|
||||
return std::find_if(std::begin(sink_details), std::end(sink_details),
|
||||
[&id](const auto& sink_detail) { return sink_detail.id == id; });
|
||||
}};
|
||||
|
||||
if (sink_id == "auto" || iter == std::end(sink_details)) {
|
||||
if (sink_id != "auto") {
|
||||
LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
|
||||
auto iter = find_backend(sink_id);
|
||||
|
||||
if (sink_id == "auto") {
|
||||
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
|
||||
// causes audio issues, in that case go with SDL.
|
||||
#if defined(HAVE_CUBEB) && defined(HAVE_SDL2)
|
||||
iter = find_backend("cubeb");
|
||||
if (iter->latency() > TargetSampleCount * 3) {
|
||||
iter = find_backend("sdl2");
|
||||
}
|
||||
// Auto-select.
|
||||
// sink_details is ordered in terms of desirability, with the best choice at the front.
|
||||
#else
|
||||
iter = std::begin(sink_details);
|
||||
#endif
|
||||
LOG_INFO(Service_Audio, "Auto-selecting the {} backend", iter->id);
|
||||
}
|
||||
|
||||
if (iter == std::end(sink_details)) {
|
||||
LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
|
||||
iter = find_backend("null");
|
||||
}
|
||||
|
||||
return *iter;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<const char*> GetSinkIDs() {
|
||||
std::vector<const char*> sink_ids(std::size(sink_details));
|
||||
std::vector<std::string_view> GetSinkIDs() {
|
||||
std::vector<std::string_view> sink_ids(std::size(sink_details));
|
||||
|
||||
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
|
||||
[](const auto& sink) { return sink.id; });
|
||||
|
||||
@@ -19,7 +19,7 @@ class Sink;
|
||||
*
|
||||
* @return Vector of available sink names.
|
||||
*/
|
||||
std::vector<const char*> GetSinkIDs();
|
||||
std::vector<std::string_view> GetSinkIDs();
|
||||
|
||||
/**
|
||||
* Gets the list of devices for a particular sink identified by the given ID.
|
||||
|
||||
@@ -17,6 +17,8 @@ endif ()
|
||||
include(GenerateSCMRev)
|
||||
|
||||
add_library(common STATIC
|
||||
address_space.cpp
|
||||
address_space.h
|
||||
algorithm.h
|
||||
alignment.h
|
||||
announce_multiplayer_room.h
|
||||
@@ -81,6 +83,8 @@ add_library(common STATIC
|
||||
microprofile.cpp
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
multi_level_page_table.cpp
|
||||
multi_level_page_table.h
|
||||
nvidia_flags.cpp
|
||||
nvidia_flags.h
|
||||
page_table.cpp
|
||||
|
||||
10
src/common/address_space.cpp
Normal file
10
src/common/address_space.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/address_space.inc"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template class Common::FlatAllocator<u32, 0, 32>;
|
||||
|
||||
}
|
||||
150
src/common/address_space.h
Normal file
150
src/common/address_space.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
template <typename VaType, size_t AddressSpaceBits>
|
||||
concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= AddressSpaceBits;
|
||||
|
||||
struct EmptyStruct {};
|
||||
|
||||
/**
|
||||
* @brief FlatAddressSpaceMap provides a generic VA->PA mapping implementation using a sorted vector
|
||||
*/
|
||||
template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa,
|
||||
bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo = EmptyStruct>
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits>
|
||||
class FlatAddressSpaceMap {
|
||||
public:
|
||||
/// The maximum VA that this AS can technically reach
|
||||
static constexpr VaType VaMaximum{(1ULL << (AddressSpaceBits - 1)) +
|
||||
((1ULL << (AddressSpaceBits - 1)) - 1)};
|
||||
|
||||
explicit FlatAddressSpaceMap(VaType va_limit,
|
||||
std::function<void(VaType, VaType)> unmap_callback = {});
|
||||
|
||||
FlatAddressSpaceMap() = default;
|
||||
|
||||
void Map(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info = {}) {
|
||||
std::scoped_lock lock(block_mutex);
|
||||
MapLocked(virt, phys, size, extra_info);
|
||||
}
|
||||
|
||||
void Unmap(VaType virt, VaType size) {
|
||||
std::scoped_lock lock(block_mutex);
|
||||
UnmapLocked(virt, size);
|
||||
}
|
||||
|
||||
VaType GetVALimit() const {
|
||||
return va_limit;
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Represents a block of memory in the AS, the physical mapping is contiguous until
|
||||
* another block with a different phys address is hit
|
||||
*/
|
||||
struct Block {
|
||||
/// VA of the block
|
||||
VaType virt{UnmappedVa};
|
||||
/// PA of the block, will increase 1-1 with VA until a new block is encountered
|
||||
PaType phys{UnmappedPa};
|
||||
[[no_unique_address]] ExtraBlockInfo extra_info;
|
||||
|
||||
Block() = default;
|
||||
|
||||
Block(VaType virt_, PaType phys_, ExtraBlockInfo extra_info_)
|
||||
: virt(virt_), phys(phys_), extra_info(extra_info_) {}
|
||||
|
||||
bool Valid() const {
|
||||
return virt != UnmappedVa;
|
||||
}
|
||||
|
||||
bool Mapped() const {
|
||||
return phys != UnmappedPa;
|
||||
}
|
||||
|
||||
bool Unmapped() const {
|
||||
return phys == UnmappedPa;
|
||||
}
|
||||
|
||||
bool operator<(const VaType& p_virt) const {
|
||||
return virt < p_virt;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Maps a PA range into the given AS region
|
||||
* @note block_mutex MUST be locked when calling this
|
||||
*/
|
||||
void MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info);
|
||||
|
||||
/**
|
||||
* @brief Unmaps the given range and merges it with other unmapped regions
|
||||
* @note block_mutex MUST be locked when calling this
|
||||
*/
|
||||
void UnmapLocked(VaType virt, VaType size);
|
||||
|
||||
std::mutex block_mutex;
|
||||
std::vector<Block> blocks{Block{}};
|
||||
|
||||
/// a soft limit on the maximum VA of the AS
|
||||
VaType va_limit{VaMaximum};
|
||||
|
||||
private:
|
||||
/// Callback called when the mappings in an region have changed
|
||||
std::function<void(VaType, VaType)> unmap_callback{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief FlatMemoryManager specialises FlatAddressSpaceMap to work as an allocator, with an
|
||||
* initial, fast linear pass and a subsequent slower pass that iterates until it finds a free block
|
||||
*/
|
||||
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits>
|
||||
class FlatAllocator
|
||||
: public FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits> {
|
||||
private:
|
||||
using Base = FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits>;
|
||||
|
||||
public:
|
||||
explicit FlatAllocator(VaType virt_start, VaType va_limit = Base::VaMaximum);
|
||||
|
||||
/**
|
||||
* @brief Allocates a region in the AS of the given size and returns its address
|
||||
*/
|
||||
VaType Allocate(VaType size);
|
||||
|
||||
/**
|
||||
* @brief Marks the given region in the AS as allocated
|
||||
*/
|
||||
void AllocateFixed(VaType virt, VaType size);
|
||||
|
||||
/**
|
||||
* @brief Frees an AS region so it can be used again
|
||||
*/
|
||||
void Free(VaType virt, VaType size);
|
||||
|
||||
VaType GetVAStart() const {
|
||||
return virt_start;
|
||||
}
|
||||
|
||||
private:
|
||||
/// The base VA of the allocator, no allocations will be below this
|
||||
VaType virt_start;
|
||||
|
||||
/**
|
||||
* The end address for the initial linear allocation pass
|
||||
* Once this reaches the AS limit the slower allocation path will be used
|
||||
*/
|
||||
VaType current_linear_alloc_end;
|
||||
};
|
||||
} // namespace Common
|
||||
366
src/common/address_space.inc
Normal file
366
src/common/address_space.inc
Normal file
@@ -0,0 +1,366 @@
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/address_space.h"
|
||||
#include "common/assert.h"
|
||||
|
||||
#define MAP_MEMBER(returnType) \
|
||||
template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, \
|
||||
bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> \
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatAddressSpaceMap< \
|
||||
VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
|
||||
#define MAP_MEMBER_CONST() \
|
||||
template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, \
|
||||
bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> \
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits> FlatAddressSpaceMap< \
|
||||
VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
|
||||
|
||||
#define MM_MEMBER(returnType) \
|
||||
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits> returnType \
|
||||
FlatMemoryManager<VaType, UnmappedVa, AddressSpaceBits>
|
||||
|
||||
#define ALLOC_MEMBER(returnType) \
|
||||
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits> returnType \
|
||||
FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
|
||||
#define ALLOC_MEMBER_CONST() \
|
||||
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
|
||||
requires AddressSpaceValid<VaType, AddressSpaceBits> \
|
||||
FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
|
||||
|
||||
namespace Common {
|
||||
MAP_MEMBER_CONST()::FlatAddressSpaceMap(VaType va_limit_,
|
||||
std::function<void(VaType, VaType)> unmap_callback_)
|
||||
: va_limit{va_limit_}, unmap_callback{std::move(unmap_callback_)} {
|
||||
if (va_limit > VaMaximum) {
|
||||
ASSERT_MSG(false, "Invalid VA limit!");
|
||||
}
|
||||
}
|
||||
|
||||
MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info) {
|
||||
VaType virt_end{virt + size};
|
||||
|
||||
if (virt_end > va_limit) {
|
||||
ASSERT_MSG(false,
|
||||
"Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
|
||||
virt_end, va_limit);
|
||||
}
|
||||
|
||||
auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
|
||||
if (block_end_successor == blocks.begin()) {
|
||||
ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: 0x{:X}", virt_end);
|
||||
}
|
||||
|
||||
auto block_end_predecessor{std::prev(block_end_successor)};
|
||||
|
||||
if (block_end_successor != blocks.end()) {
|
||||
// We have blocks in front of us, if one is directly in front then we don't have to add a
|
||||
// tail
|
||||
if (block_end_successor->virt != virt_end) {
|
||||
PaType tailPhys{[&]() -> PaType {
|
||||
if constexpr (!PaContigSplit) {
|
||||
// Always propagate unmapped regions rather than calculating offset
|
||||
return block_end_predecessor->phys;
|
||||
} else {
|
||||
if (block_end_predecessor->Unmapped()) {
|
||||
// Always propagate unmapped regions rather than calculating offset
|
||||
return block_end_predecessor->phys;
|
||||
} else {
|
||||
return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
|
||||
}
|
||||
}
|
||||
}()};
|
||||
|
||||
if (block_end_predecessor->virt >= virt) {
|
||||
// If this block's start would be overlapped by the map then reuse it as a tail
|
||||
// block
|
||||
block_end_predecessor->virt = virt_end;
|
||||
block_end_predecessor->phys = tailPhys;
|
||||
block_end_predecessor->extra_info = block_end_predecessor->extra_info;
|
||||
|
||||
// No longer predecessor anymore
|
||||
block_end_successor = block_end_predecessor--;
|
||||
} else {
|
||||
// Else insert a new one and we're done
|
||||
blocks.insert(block_end_successor,
|
||||
{Block(virt, phys, extra_info),
|
||||
Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
|
||||
if (unmap_callback) {
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// block_end_predecessor will always be unmapped as blocks has to be terminated by an
|
||||
// unmapped chunk
|
||||
if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) {
|
||||
// Move the unmapped block start backwards
|
||||
block_end_predecessor->virt = virt_end;
|
||||
|
||||
// No longer predecessor anymore
|
||||
block_end_successor = block_end_predecessor--;
|
||||
} else {
|
||||
// Else insert a new one and we're done
|
||||
blocks.insert(block_end_successor,
|
||||
{Block(virt, phys, extra_info), Block(virt_end, UnmappedPa, {})});
|
||||
if (unmap_callback) {
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto block_start_successor{block_end_successor};
|
||||
|
||||
// Walk the block vector to find the start successor as this is more efficient than another
|
||||
// binary search in most scenarios
|
||||
while (std::prev(block_start_successor)->virt >= virt) {
|
||||
block_start_successor--;
|
||||
}
|
||||
|
||||
// Check that the start successor is either the end block or something in between
|
||||
if (block_start_successor->virt > virt_end) {
|
||||
ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
|
||||
} else if (block_start_successor->virt == virt_end) {
|
||||
// We need to create a new block as there are none spare that we would overwrite
|
||||
blocks.insert(block_start_successor, Block(virt, phys, extra_info));
|
||||
} else {
|
||||
// Erase overwritten blocks
|
||||
if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
|
||||
blocks.erase(eraseStart, block_end_successor);
|
||||
}
|
||||
|
||||
// Reuse a block that would otherwise be overwritten as a start block
|
||||
block_start_successor->virt = virt;
|
||||
block_start_successor->phys = phys;
|
||||
block_start_successor->extra_info = extra_info;
|
||||
}
|
||||
|
||||
if (unmap_callback) {
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
}
|
||||
|
||||
MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
|
||||
VaType virt_end{virt + size};
|
||||
|
||||
if (virt_end > va_limit) {
|
||||
ASSERT_MSG(false,
|
||||
"Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
|
||||
virt_end, va_limit);
|
||||
}
|
||||
|
||||
auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
|
||||
if (block_end_successor == blocks.begin()) {
|
||||
ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: 0x{:X}",
|
||||
virt_end);
|
||||
}
|
||||
|
||||
auto block_end_predecessor{std::prev(block_end_successor)};
|
||||
|
||||
auto walk_back_to_predecessor{[&](auto iter) {
|
||||
while (iter->virt >= virt) {
|
||||
iter--;
|
||||
}
|
||||
|
||||
return iter;
|
||||
}};
|
||||
|
||||
auto erase_blocks_with_end_unmapped{[&](auto unmappedEnd) {
|
||||
auto block_start_predecessor{walk_back_to_predecessor(unmappedEnd)};
|
||||
auto block_start_successor{std::next(block_start_predecessor)};
|
||||
|
||||
auto eraseEnd{[&]() {
|
||||
if (block_start_predecessor->Unmapped()) {
|
||||
// If the start predecessor is unmapped then we can erase everything in our region
|
||||
// and be done
|
||||
return std::next(unmappedEnd);
|
||||
} else {
|
||||
// Else reuse the end predecessor as the start of our unmapped region then erase all
|
||||
// up to it
|
||||
unmappedEnd->virt = virt;
|
||||
return unmappedEnd;
|
||||
}
|
||||
}()};
|
||||
|
||||
// We can't have two unmapped regions after each other
|
||||
if (eraseEnd != blocks.end() &&
|
||||
(eraseEnd == block_start_successor ||
|
||||
(block_start_predecessor->Unmapped() && eraseEnd->Unmapped()))) {
|
||||
ASSERT_MSG(false, "Multiple contiguous unmapped regions are unsupported!");
|
||||
}
|
||||
|
||||
blocks.erase(block_start_successor, eraseEnd);
|
||||
}};
|
||||
|
||||
// We can avoid any splitting logic if these are the case
|
||||
if (block_end_predecessor->Unmapped()) {
|
||||
if (block_end_predecessor->virt > virt) {
|
||||
erase_blocks_with_end_unmapped(block_end_predecessor);
|
||||
}
|
||||
|
||||
if (unmap_callback) {
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
|
||||
return; // The region is unmapped, bail out early
|
||||
} else if (block_end_successor->virt == virt_end && block_end_successor->Unmapped()) {
|
||||
erase_blocks_with_end_unmapped(block_end_successor);
|
||||
|
||||
if (unmap_callback) {
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
|
||||
return; // The region is unmapped here and doesn't need splitting, bail out early
|
||||
} else if (block_end_successor == blocks.end()) {
|
||||
// This should never happen as the end should always follow an unmapped block
|
||||
ASSERT_MSG(false, "Unexpected Memory Manager state!");
|
||||
} else if (block_end_successor->virt != virt_end) {
|
||||
// If one block is directly in front then we don't have to add a tail
|
||||
|
||||
// The previous block is mapped so we will need to add a tail with an offset
|
||||
PaType tailPhys{[&]() {
|
||||
if constexpr (PaContigSplit) {
|
||||
return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
|
||||
} else {
|
||||
return block_end_predecessor->phys;
|
||||
}
|
||||
}()};
|
||||
|
||||
if (block_end_predecessor->virt >= virt) {
|
||||
// If this block's start would be overlapped by the unmap then reuse it as a tail block
|
||||
block_end_predecessor->virt = virt_end;
|
||||
block_end_predecessor->phys = tailPhys;
|
||||
|
||||
// No longer predecessor anymore
|
||||
block_end_successor = block_end_predecessor--;
|
||||
} else {
|
||||
blocks.insert(block_end_successor,
|
||||
{Block(virt, UnmappedPa, {}),
|
||||
Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
|
||||
if (unmap_callback) {
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
|
||||
// The previous block is mapped and ends before
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the block vector to find the start predecessor as this is more efficient than another
|
||||
// binary search in most scenarios
|
||||
auto block_start_predecessor{walk_back_to_predecessor(block_end_successor)};
|
||||
auto block_start_successor{std::next(block_start_predecessor)};
|
||||
|
||||
if (block_start_successor->virt > virt_end) {
|
||||
ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
|
||||
} else if (block_start_successor->virt == virt_end) {
|
||||
// There are no blocks between the start and the end that would let us skip inserting a new
|
||||
// one for head
|
||||
|
||||
// The previous block is may be unmapped, if so we don't need to insert any unmaps after it
|
||||
if (block_start_predecessor->Mapped()) {
|
||||
blocks.insert(block_start_successor, Block(virt, UnmappedPa, {}));
|
||||
}
|
||||
} else if (block_start_predecessor->Unmapped()) {
|
||||
// If the previous block is unmapped
|
||||
blocks.erase(block_start_successor, block_end_predecessor);
|
||||
} else {
|
||||
// Erase overwritten blocks, skipping the first one as we have written the unmapped start
|
||||
// block there
|
||||
if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
|
||||
blocks.erase(eraseStart, block_end_successor);
|
||||
}
|
||||
|
||||
// Add in the unmapped block header
|
||||
block_start_successor->virt = virt;
|
||||
block_start_successor->phys = UnmappedPa;
|
||||
}
|
||||
|
||||
if (unmap_callback)
|
||||
unmap_callback(virt, size);
|
||||
}
|
||||
|
||||
ALLOC_MEMBER_CONST()::FlatAllocator(VaType virt_start_, VaType va_limit_)
|
||||
: Base{va_limit_}, virt_start{virt_start_}, current_linear_alloc_end{virt_start_} {}
|
||||
|
||||
ALLOC_MEMBER(VaType)::Allocate(VaType size) {
|
||||
std::scoped_lock lock(this->block_mutex);
|
||||
|
||||
VaType alloc_start{UnmappedVa};
|
||||
VaType alloc_end{current_linear_alloc_end + size};
|
||||
|
||||
// Avoid searching backwards in the address space if possible
|
||||
if (alloc_end >= current_linear_alloc_end && alloc_end <= this->va_limit) {
|
||||
auto alloc_end_successor{
|
||||
std::lower_bound(this->blocks.begin(), this->blocks.end(), alloc_end)};
|
||||
if (alloc_end_successor == this->blocks.begin()) {
|
||||
ASSERT_MSG(false, "First block in AS map is invalid!");
|
||||
}
|
||||
|
||||
auto alloc_end_predecessor{std::prev(alloc_end_successor)};
|
||||
if (alloc_end_predecessor->virt <= current_linear_alloc_end) {
|
||||
alloc_start = current_linear_alloc_end;
|
||||
} else {
|
||||
// Skip over fixed any mappings in front of us
|
||||
while (alloc_end_successor != this->blocks.end()) {
|
||||
if (alloc_end_successor->virt - alloc_end_predecessor->virt < size ||
|
||||
alloc_end_predecessor->Mapped()) {
|
||||
alloc_start = alloc_end_predecessor->virt;
|
||||
break;
|
||||
}
|
||||
|
||||
alloc_end_predecessor = alloc_end_successor++;
|
||||
|
||||
// Use the VA limit to calculate if we can fit in the final block since it has no
|
||||
// successor
|
||||
if (alloc_end_successor == this->blocks.end()) {
|
||||
alloc_end = alloc_end_predecessor->virt + size;
|
||||
|
||||
if (alloc_end >= alloc_end_predecessor->virt && alloc_end <= this->va_limit) {
|
||||
alloc_start = alloc_end_predecessor->virt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alloc_start != UnmappedVa) {
|
||||
current_linear_alloc_end = alloc_start + size;
|
||||
} else { // If linear allocation overflows the AS then find a gap
|
||||
if (this->blocks.size() <= 2) {
|
||||
ASSERT_MSG(false, "Unexpected allocator state!");
|
||||
}
|
||||
|
||||
auto search_predecessor{this->blocks.begin()};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
} else {
|
||||
return {}; // AS is full
|
||||
}
|
||||
}
|
||||
|
||||
this->MapLocked(alloc_start, true, size, {});
|
||||
return alloc_start;
|
||||
}
|
||||
|
||||
ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
|
||||
this->Map(virt, true, size);
|
||||
}
|
||||
|
||||
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
|
||||
this->Unmap(virt, size);
|
||||
}
|
||||
} // namespace Common
|
||||
@@ -24,4 +24,12 @@ template <class ForwardIt, class T, class Compare = std::less<>>
|
||||
return first != last && !comp(value, *first) ? first : last;
|
||||
}
|
||||
|
||||
template <typename T, typename Func, typename... Args>
|
||||
T FoldRight(T initial_value, Func&& func, Args&&... args) {
|
||||
T value{initial_value};
|
||||
const auto high_func = [&value, &func]<typename U>(U x) { value = func(value, x); };
|
||||
(std::invoke(high_func, std::forward<Args>(args)), ...);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -18,4 +18,11 @@ struct PairHash {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IdentityHash {
|
||||
[[nodiscard]] size_t operator()(T value) const noexcept {
|
||||
return static_cast<size_t>(value);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -277,8 +277,9 @@ struct CallbackStatus {
|
||||
BodyColorStatus color_status{};
|
||||
BatteryStatus battery_status{};
|
||||
VibrationStatus vibration_status{};
|
||||
CameraStatus camera_status{};
|
||||
NfcStatus nfc_status{};
|
||||
CameraFormat camera_status{CameraFormat::None};
|
||||
NfcState nfc_status{NfcState::Unknown};
|
||||
std::vector<u8> raw_data{};
|
||||
};
|
||||
|
||||
// Triggered once every input change
|
||||
|
||||
@@ -219,7 +219,7 @@ private:
|
||||
|
||||
void StartBackendThread() {
|
||||
backend_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("yuzu:Log");
|
||||
Common::SetCurrentThreadName("Logger");
|
||||
Entry entry;
|
||||
const auto write_logs = [this, &entry]() {
|
||||
ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
|
||||
|
||||
9
src/common/multi_level_page_table.cpp
Normal file
9
src/common/multi_level_page_table.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/multi_level_page_table.inc"
|
||||
|
||||
namespace Common {
|
||||
template class Common::MultiLevelPageTable<u64>;
|
||||
template class Common::MultiLevelPageTable<u32>;
|
||||
} // namespace Common
|
||||
78
src/common/multi_level_page_table.h
Normal file
78
src/common/multi_level_page_table.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename BaseAddr>
|
||||
class MultiLevelPageTable final {
|
||||
public:
|
||||
constexpr MultiLevelPageTable() = default;
|
||||
explicit MultiLevelPageTable(std::size_t address_space_bits, std::size_t first_level_bits,
|
||||
std::size_t page_bits);
|
||||
|
||||
~MultiLevelPageTable() noexcept;
|
||||
|
||||
MultiLevelPageTable(const MultiLevelPageTable&) = delete;
|
||||
MultiLevelPageTable& operator=(const MultiLevelPageTable&) = delete;
|
||||
|
||||
MultiLevelPageTable(MultiLevelPageTable&& other) noexcept
|
||||
: address_space_bits{std::exchange(other.address_space_bits, 0)},
|
||||
first_level_bits{std::exchange(other.first_level_bits, 0)}, page_bits{std::exchange(
|
||||
other.page_bits, 0)},
|
||||
first_level_shift{std::exchange(other.first_level_shift, 0)},
|
||||
first_level_chunk_size{std::exchange(other.first_level_chunk_size, 0)},
|
||||
first_level_map{std::move(other.first_level_map)}, base_ptr{std::exchange(other.base_ptr,
|
||||
nullptr)} {}
|
||||
|
||||
MultiLevelPageTable& operator=(MultiLevelPageTable&& other) noexcept {
|
||||
address_space_bits = std::exchange(other.address_space_bits, 0);
|
||||
first_level_bits = std::exchange(other.first_level_bits, 0);
|
||||
page_bits = std::exchange(other.page_bits, 0);
|
||||
first_level_shift = std::exchange(other.first_level_shift, 0);
|
||||
first_level_chunk_size = std::exchange(other.first_level_chunk_size, 0);
|
||||
alloc_size = std::exchange(other.alloc_size, 0);
|
||||
first_level_map = std::move(other.first_level_map);
|
||||
base_ptr = std::exchange(other.base_ptr, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ReserveRange(u64 start, std::size_t size);
|
||||
|
||||
[[nodiscard]] const BaseAddr& operator[](std::size_t index) const {
|
||||
return base_ptr[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] BaseAddr& operator[](std::size_t index) {
|
||||
return base_ptr[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] BaseAddr* data() {
|
||||
return base_ptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] const BaseAddr* data() const {
|
||||
return base_ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void AllocateLevel(u64 level);
|
||||
|
||||
std::size_t address_space_bits{};
|
||||
std::size_t first_level_bits{};
|
||||
std::size_t page_bits{};
|
||||
std::size_t first_level_shift{};
|
||||
std::size_t first_level_chunk_size{};
|
||||
std::size_t alloc_size{};
|
||||
std::vector<void*> first_level_map{};
|
||||
BaseAddr* base_ptr{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
84
src/common/multi_level_page_table.inc
Normal file
84
src/common/multi_level_page_table.inc
Normal file
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/multi_level_page_table.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename BaseAddr>
|
||||
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
|
||||
std::size_t first_level_bits_,
|
||||
std::size_t page_bits_)
|
||||
: address_space_bits{address_space_bits_},
|
||||
first_level_bits{first_level_bits_}, page_bits{page_bits_} {
|
||||
if (page_bits == 0) {
|
||||
return;
|
||||
}
|
||||
first_level_shift = address_space_bits - first_level_bits;
|
||||
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
|
||||
alloc_size = (1ULL << (address_space_bits - page_bits)) * sizeof(BaseAddr);
|
||||
std::size_t first_level_size = 1ULL << first_level_bits;
|
||||
first_level_map.resize(first_level_size, nullptr);
|
||||
#ifdef _WIN32
|
||||
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
|
||||
#else
|
||||
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
base = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
ASSERT(base);
|
||||
base_ptr = reinterpret_cast<BaseAddr*>(base);
|
||||
}
|
||||
|
||||
template <typename BaseAddr>
|
||||
MultiLevelPageTable<BaseAddr>::~MultiLevelPageTable() noexcept {
|
||||
if (!base_ptr) {
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
ASSERT(VirtualFree(base_ptr, 0, MEM_RELEASE));
|
||||
#else
|
||||
ASSERT(munmap(base_ptr, alloc_size) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename BaseAddr>
|
||||
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
|
||||
const u64 new_start = start >> first_level_shift;
|
||||
const u64 new_end = (start + size) >> first_level_shift;
|
||||
for (u64 i = new_start; i <= new_end; i++) {
|
||||
if (!first_level_map[i]) {
|
||||
AllocateLevel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BaseAddr>
|
||||
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
|
||||
void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
|
||||
#ifdef _WIN32
|
||||
void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
|
||||
#else
|
||||
void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
base = nullptr;
|
||||
}
|
||||
#endif
|
||||
ASSERT(base);
|
||||
|
||||
first_level_map[level] = base;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -431,7 +431,7 @@ struct Values {
|
||||
FullscreenMode::Exclusive,
|
||||
#endif
|
||||
FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"};
|
||||
SwitchableSetting<int, true> aspect_ratio{0, 0, 3, "aspect_ratio"};
|
||||
SwitchableSetting<int, true> aspect_ratio{0, 0, 4, "aspect_ratio"};
|
||||
SwitchableSetting<int, true> max_anisotropy{0, 0, 5, "max_anisotropy"};
|
||||
SwitchableSetting<bool> use_speed_limit{true, "use_speed_limit"};
|
||||
SwitchableSetting<u16, true> speed_limit{100, 0, 9999, "speed_limit"};
|
||||
|
||||
@@ -138,8 +138,6 @@ add_library(core STATIC
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
frontend/framebuffer_layout.h
|
||||
hardware_interrupt_manager.cpp
|
||||
hardware_interrupt_manager.h
|
||||
hid/emulated_console.cpp
|
||||
hid/emulated_console.h
|
||||
hid/emulated_controller.cpp
|
||||
@@ -263,8 +261,6 @@ add_library(core STATIC
|
||||
hle/kernel/k_worker_task.h
|
||||
hle/kernel/k_worker_task_manager.cpp
|
||||
hle/kernel/k_worker_task_manager.h
|
||||
hle/kernel/k_writable_event.cpp
|
||||
hle/kernel/k_writable_event.h
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory_types.h
|
||||
@@ -550,6 +546,12 @@ add_library(core STATIC
|
||||
hle/service/ns/ns.h
|
||||
hle/service/ns/pdm_qry.cpp
|
||||
hle/service/ns/pdm_qry.h
|
||||
hle/service/nvdrv/core/container.cpp
|
||||
hle/service/nvdrv/core/container.h
|
||||
hle/service/nvdrv/core/nvmap.cpp
|
||||
hle/service/nvdrv/core/nvmap.h
|
||||
hle/service/nvdrv/core/syncpoint_manager.cpp
|
||||
hle/service/nvdrv/core/syncpoint_manager.h
|
||||
hle/service/nvdrv/devices/nvdevice.h
|
||||
hle/service/nvdrv/devices/nvdisp_disp0.cpp
|
||||
hle/service/nvdrv/devices/nvdisp_disp0.h
|
||||
@@ -578,8 +580,6 @@ add_library(core STATIC
|
||||
hle/service/nvdrv/nvdrv_interface.h
|
||||
hle/service/nvdrv/nvmemp.cpp
|
||||
hle/service/nvdrv/nvmemp.h
|
||||
hle/service/nvdrv/syncpoint_manager.cpp
|
||||
hle/service/nvdrv/syncpoint_manager.h
|
||||
hle/service/nvflinger/binder.h
|
||||
hle/service/nvflinger/buffer_item.h
|
||||
hle/service/nvflinger/buffer_item_consumer.cpp
|
||||
|
||||
@@ -111,6 +111,7 @@ public:
|
||||
LOG_ERROR(Core_ARM,
|
||||
"Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
|
||||
num_instructions, memory.Read32(pc));
|
||||
ReturnException(pc, ARM_Interface::no_execute);
|
||||
}
|
||||
|
||||
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hardware_interrupt_manager.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_memory_manager.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
@@ -51,6 +50,7 @@
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@@ -215,6 +215,7 @@ struct System::Impl {
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
|
||||
host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
|
||||
gpu_core = VideoCore::CreateGPU(emu_window, system);
|
||||
if (!gpu_core) {
|
||||
return SystemResultStatus::ErrorVideoCore;
|
||||
@@ -224,7 +225,6 @@ struct System::Impl {
|
||||
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
||||
services = std::make_unique<Service::Services>(service_manager, system);
|
||||
interrupt_manager = std::make_unique<Hardware::InterruptManager>(system);
|
||||
|
||||
// Initialize time manager, which must happen after kernel is created
|
||||
time_manager.Initialize();
|
||||
@@ -373,6 +373,7 @@ struct System::Impl {
|
||||
app_loader.reset();
|
||||
audio_core.reset();
|
||||
gpu_core.reset();
|
||||
host1x_core.reset();
|
||||
perf_stats.reset();
|
||||
kernel.Shutdown();
|
||||
memory.Reset();
|
||||
@@ -450,7 +451,7 @@ struct System::Impl {
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
|
||||
std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
|
||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
||||
Core::Memory::Memory memory;
|
||||
@@ -668,12 +669,12 @@ const Tegra::GPU& System::GPU() const {
|
||||
return *impl->gpu_core;
|
||||
}
|
||||
|
||||
Core::Hardware::InterruptManager& System::InterruptManager() {
|
||||
return *impl->interrupt_manager;
|
||||
Tegra::Host1x::Host1x& System::Host1x() {
|
||||
return *impl->host1x_core;
|
||||
}
|
||||
|
||||
const Core::Hardware::InterruptManager& System::InterruptManager() const {
|
||||
return *impl->interrupt_manager;
|
||||
const Tegra::Host1x::Host1x& System::Host1x() const {
|
||||
return *impl->host1x_core;
|
||||
}
|
||||
|
||||
VideoCore::RendererBase& System::Renderer() {
|
||||
|
||||
@@ -74,6 +74,9 @@ class TimeManager;
|
||||
namespace Tegra {
|
||||
class DebugContext;
|
||||
class GPU;
|
||||
namespace Host1x {
|
||||
class Host1x;
|
||||
} // namespace Host1x
|
||||
} // namespace Tegra
|
||||
|
||||
namespace VideoCore {
|
||||
@@ -88,10 +91,6 @@ namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core::Hardware {
|
||||
class InterruptManager;
|
||||
}
|
||||
|
||||
namespace Core::HID {
|
||||
class HIDCore;
|
||||
}
|
||||
@@ -260,6 +259,12 @@ public:
|
||||
/// Gets an immutable reference to the GPU interface.
|
||||
[[nodiscard]] const Tegra::GPU& GPU() const;
|
||||
|
||||
/// Gets a mutable reference to the Host1x interface
|
||||
[[nodiscard]] Tegra::Host1x::Host1x& Host1x();
|
||||
|
||||
/// Gets an immutable reference to the Host1x interface.
|
||||
[[nodiscard]] const Tegra::Host1x::Host1x& Host1x() const;
|
||||
|
||||
/// Gets a mutable reference to the renderer.
|
||||
[[nodiscard]] VideoCore::RendererBase& Renderer();
|
||||
|
||||
@@ -296,12 +301,6 @@ public:
|
||||
/// Provides a constant reference to the core timing instance.
|
||||
[[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
|
||||
|
||||
/// Provides a reference to the interrupt manager instance.
|
||||
[[nodiscard]] Core::Hardware::InterruptManager& InterruptManager();
|
||||
|
||||
/// Provides a constant reference to the interrupt manager instance.
|
||||
[[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
[[nodiscard]] Kernel::KernelCore& Kernel();
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ CoreTiming::CoreTiming()
|
||||
CoreTiming::~CoreTiming() = default;
|
||||
|
||||
void CoreTiming::ThreadEntry(CoreTiming& instance) {
|
||||
constexpr char name[] = "yuzu:HostTiming";
|
||||
constexpr char name[] = "HostTiming";
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
@@ -270,6 +270,7 @@ void CoreTiming::ThreadLoop() {
|
||||
// There are more events left in the queue, wait until the next event.
|
||||
const auto wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
if (wait_time > 0) {
|
||||
#ifdef _WIN32
|
||||
// Assume a timer resolution of 1ms.
|
||||
static constexpr s64 TimerResolutionNS = 1000000;
|
||||
|
||||
@@ -287,6 +288,9 @@ void CoreTiming::ThreadLoop() {
|
||||
if (event.IsSet()) {
|
||||
event.Reset();
|
||||
}
|
||||
#else
|
||||
event.WaitFor(std::chrono::nanoseconds(wait_time));
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Queue is empty, wait until another event is scheduled and signals us to continue.
|
||||
|
||||
@@ -189,9 +189,9 @@ void CpuManager::RunThread(std::size_t core) {
|
||||
system.RegisterCoreThread(core);
|
||||
std::string name;
|
||||
if (is_multicore) {
|
||||
name = "yuzu:CPUCore_" + std::to_string(core);
|
||||
name = "CPUCore_" + std::to_string(core);
|
||||
} else {
|
||||
name = "yuzu:CPUThread";
|
||||
name = "CPUThread";
|
||||
}
|
||||
MicroProfileOnThreadCreate(name.c_str());
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
|
||||
@@ -140,7 +140,7 @@ private:
|
||||
}
|
||||
|
||||
void ThreadLoop(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("yuzu:Debugger");
|
||||
Common::SetCurrentThreadName("Debugger");
|
||||
|
||||
// Set up the client signals for new data.
|
||||
AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
|
||||
|
||||
@@ -33,11 +33,55 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
||||
return Loader::ResultStatus::ErrorBadACIHeader;
|
||||
}
|
||||
|
||||
if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset)) {
|
||||
// Load acid_file_access per-component instead of the entire struct, since this struct does not
|
||||
// reflect the layout of the real data.
|
||||
std::size_t current_offset = acid_header.fac_offset;
|
||||
if (sizeof(FileAccessControl::version) != file->ReadBytes(&acid_file_access.version,
|
||||
sizeof(FileAccessControl::version),
|
||||
current_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
}
|
||||
if (sizeof(FileAccessControl::permissions) !=
|
||||
file->ReadBytes(&acid_file_access.permissions, sizeof(FileAccessControl::permissions),
|
||||
current_offset += sizeof(FileAccessControl::version) + 3)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
}
|
||||
if (sizeof(FileAccessControl::unknown) !=
|
||||
file->ReadBytes(&acid_file_access.unknown, sizeof(FileAccessControl::unknown),
|
||||
current_offset + sizeof(FileAccessControl::permissions))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessControl;
|
||||
}
|
||||
|
||||
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset)) {
|
||||
// Load aci_file_access per-component instead of the entire struct, same as acid_file_access
|
||||
current_offset = aci_header.fah_offset;
|
||||
if (sizeof(FileAccessHeader::version) != file->ReadBytes(&aci_file_access.version,
|
||||
sizeof(FileAccessHeader::version),
|
||||
current_offset)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::permissions) !=
|
||||
file->ReadBytes(&aci_file_access.permissions, sizeof(FileAccessHeader::permissions),
|
||||
current_offset += sizeof(FileAccessHeader::version) + 3)) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_offset) !=
|
||||
file->ReadBytes(&aci_file_access.unk_offset, sizeof(FileAccessHeader::unk_offset),
|
||||
current_offset += sizeof(FileAccessHeader::permissions))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_size) !=
|
||||
file->ReadBytes(&aci_file_access.unk_size, sizeof(FileAccessHeader::unk_size),
|
||||
current_offset += sizeof(FileAccessHeader::unk_offset))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_offset_2) !=
|
||||
file->ReadBytes(&aci_file_access.unk_offset_2, sizeof(FileAccessHeader::unk_offset_2),
|
||||
current_offset += sizeof(FileAccessHeader::unk_size))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
if (sizeof(FileAccessHeader::unk_size_2) !=
|
||||
file->ReadBytes(&aci_file_access.unk_size_2, sizeof(FileAccessHeader::unk_size_2),
|
||||
current_offset + sizeof(FileAccessHeader::unk_offset_2))) {
|
||||
return Loader::ResultStatus::ErrorBadFileAccessHeader;
|
||||
}
|
||||
|
||||
@@ -152,9 +196,7 @@ void ProgramMetadata::Print() const {
|
||||
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO");
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
|
||||
u64_le permissions_l; // local copy to fix alignment error
|
||||
std::memcpy(&permissions_l, &acid_file_access.permissions, sizeof(permissions_l));
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", permissions_l);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
||||
|
||||
// Begin ACI0 printing (actual perms, unsigned)
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data());
|
||||
|
||||
@@ -144,20 +144,18 @@ private:
|
||||
|
||||
static_assert(sizeof(AciHeader) == 0x40, "ACI0 header structure size is wrong");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// FileAccessControl and FileAccessHeader need loaded per-component: this layout does not
|
||||
// reflect the real layout to avoid reference binding to misaligned addresses
|
||||
struct FileAccessControl {
|
||||
u8 version;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
// 3 padding bytes
|
||||
u64_le permissions;
|
||||
std::array<u8, 0x20> unknown;
|
||||
};
|
||||
|
||||
static_assert(sizeof(FileAccessControl) == 0x2C, "FS access control structure size is wrong");
|
||||
|
||||
struct FileAccessHeader {
|
||||
u8 version;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
// 3 padding bytes
|
||||
u64_le permissions;
|
||||
u32_le unk_offset;
|
||||
u32_le unk_size;
|
||||
@@ -165,10 +163,6 @@ private:
|
||||
u32_le unk_size_2;
|
||||
};
|
||||
|
||||
static_assert(sizeof(FileAccessHeader) == 0x1C, "FS access header structure size is wrong");
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
Header npdm_header;
|
||||
AciHeader aci_header;
|
||||
AcidHeader acid_header;
|
||||
|
||||
@@ -67,6 +67,8 @@ float EmulationAspectRatio(AspectRatio aspect, float window_aspect_ratio) {
|
||||
return 3.0f / 4.0f;
|
||||
case AspectRatio::R21_9:
|
||||
return 9.0f / 21.0f;
|
||||
case AspectRatio::R16_10:
|
||||
return 10.0f / 16.0f;
|
||||
case AspectRatio::StretchToWindow:
|
||||
return window_aspect_ratio;
|
||||
default:
|
||||
|
||||
@@ -27,6 +27,7 @@ enum class AspectRatio {
|
||||
Default,
|
||||
R4_3,
|
||||
R21_9,
|
||||
R16_10,
|
||||
StretchToWindow,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hardware_interrupt_manager.h"
|
||||
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Core::Hardware {
|
||||
|
||||
InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
|
||||
gpu_interrupt_event = Core::Timing::CreateEvent(
|
||||
"GPUInterrupt",
|
||||
[this](std::uintptr_t message, u64 time,
|
||||
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
|
||||
auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
|
||||
const u32 syncpt = static_cast<u32>(message >> 32);
|
||||
const u32 value = static_cast<u32>(message);
|
||||
nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
|
||||
return std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
InterruptManager::~InterruptManager() = default;
|
||||
|
||||
void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
|
||||
const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
|
||||
system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg);
|
||||
}
|
||||
|
||||
} // namespace Core::Hardware
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace Core::Hardware {
|
||||
|
||||
class InterruptManager {
|
||||
public:
|
||||
explicit InterruptManager(Core::System& system);
|
||||
~InterruptManager();
|
||||
|
||||
void GPUInterruptSyncpt(u32 syncpoint_id, u32 value);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
std::shared_ptr<Core::Timing::EventType> gpu_interrupt_event;
|
||||
};
|
||||
|
||||
} // namespace Core::Hardware
|
||||
@@ -1017,9 +1017,11 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode)
|
||||
auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_output_device = output_devices[3];
|
||||
|
||||
nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
|
||||
|
||||
return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None;
|
||||
return virtual_nfc_result == Common::Input::PollingError::None ||
|
||||
mapped_nfc_result == Common::Input::PollingError::None;
|
||||
}
|
||||
|
||||
bool EmulatedController::SetCameraFormat(
|
||||
|
||||
@@ -277,7 +277,10 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
|
||||
Common::Input::CameraStatus camera{};
|
||||
switch (callback.type) {
|
||||
case Common::Input::InputType::IrSensor:
|
||||
camera = callback.camera_status;
|
||||
camera = {
|
||||
.format = callback.camera_status,
|
||||
.data = callback.raw_data,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type);
|
||||
@@ -291,7 +294,10 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
|
||||
Common::Input::NfcStatus nfc{};
|
||||
switch (callback.type) {
|
||||
case Common::Input::InputType::Nfc:
|
||||
return callback.nfc_status;
|
||||
nfc = {
|
||||
.state = callback.nfc_status,
|
||||
.data = callback.raw_data,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
|
||||
|
||||
@@ -14,7 +14,7 @@ enum class CameraAmbientNoiseLevel : u32 {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Unkown3, // This level can't be reached
|
||||
Unknown3, // This level can't be reached
|
||||
};
|
||||
|
||||
// This is nn::irsensor::CameraLightTarget
|
||||
@@ -75,9 +75,9 @@ enum class IrCameraStatus : u32 {
|
||||
enum class IrCameraInternalStatus : u32 {
|
||||
Stopped,
|
||||
FirmwareUpdateNeeded,
|
||||
Unkown2,
|
||||
Unkown3,
|
||||
Unkown4,
|
||||
Unknown2,
|
||||
Unknown3,
|
||||
Unknown4,
|
||||
FirmwareVersionRequested,
|
||||
FirmwareVersionIsInvalid,
|
||||
Ready,
|
||||
@@ -121,20 +121,20 @@ enum class IrSensorFunctionLevel : u8 {
|
||||
|
||||
// This is nn::irsensor::MomentProcessorPreprocess
|
||||
enum class MomentProcessorPreprocess : u32 {
|
||||
Unkown0,
|
||||
Unkown1,
|
||||
Unknown0,
|
||||
Unknown1,
|
||||
};
|
||||
|
||||
// This is nn::irsensor::PackedMomentProcessorPreprocess
|
||||
enum class PackedMomentProcessorPreprocess : u8 {
|
||||
Unkown0,
|
||||
Unkown1,
|
||||
Unknown0,
|
||||
Unknown1,
|
||||
};
|
||||
|
||||
// This is nn::irsensor::PointingStatus
|
||||
enum class PointingStatus : u32 {
|
||||
Unkown0,
|
||||
Unkown1,
|
||||
Unknown0,
|
||||
Unknown1,
|
||||
};
|
||||
|
||||
struct IrsRect {
|
||||
|
||||
@@ -152,7 +152,8 @@ public:
|
||||
Kernel::LimitableResource::Sessions, 1);
|
||||
|
||||
auto* session = Kernel::KSession::Create(kernel);
|
||||
session->Initialize(nullptr, iface->GetServiceName());
|
||||
session->Initialize(nullptr, iface->GetServiceName(),
|
||||
std::make_shared<Kernel::SessionRequestManager>(kernel));
|
||||
|
||||
context->AddMoveObject(&session->GetClientSession());
|
||||
iface->ClientConnected(&session->GetServerSession());
|
||||
|
||||
@@ -43,13 +43,13 @@ class Domain;
|
||||
class HLERequestContext;
|
||||
class KAutoObject;
|
||||
class KernelCore;
|
||||
class KEvent;
|
||||
class KHandleTable;
|
||||
class KProcess;
|
||||
class KServerSession;
|
||||
class KThread;
|
||||
class KReadableEvent;
|
||||
class KSession;
|
||||
class KWritableEvent;
|
||||
class ServiceThread;
|
||||
|
||||
enum class ThreadWakeupReason;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "core/hle/kernel/k_synchronization_object.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
#include "core/hle/kernel/k_writable_event.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -42,13 +41,12 @@ static_assert(ClassToken<KPort> == 0b10000101'00000000);
|
||||
static_assert(ClassToken<KSession> == 0b00011001'00000000);
|
||||
static_assert(ClassToken<KSharedMemory> == 0b00101001'00000000);
|
||||
static_assert(ClassToken<KEvent> == 0b01001001'00000000);
|
||||
static_assert(ClassToken<KWritableEvent> == 0b10001001'00000000);
|
||||
// static_assert(ClassToken<KLightClientSession> == 0b00110001'00000000);
|
||||
// static_assert(ClassToken<KLightServerSession> == 0b01010001'00000000);
|
||||
static_assert(ClassToken<KTransferMemory> == 0b10010001'00000000);
|
||||
static_assert(ClassToken<KTransferMemory> == 0b01010001'00000000);
|
||||
// static_assert(ClassToken<KDeviceAddressSpace> == 0b01100001'00000000);
|
||||
// static_assert(ClassToken<KSessionRequest> == 0b10100001'00000000);
|
||||
static_assert(ClassToken<KCodeMemory> == 0b11000001'00000000);
|
||||
static_assert(ClassToken<KCodeMemory> == 0b10100001'00000000);
|
||||
|
||||
// Ensure that the token hierarchy is correct.
|
||||
|
||||
@@ -73,13 +71,12 @@ static_assert(ClassToken<KPort> == ((0b10000101 << 8) | ClassToken<KAutoObject>)
|
||||
static_assert(ClassToken<KSession> == ((0b00011001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KSharedMemory> == ((0b00101001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KEvent> == ((0b01001001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KWritableEvent> == ((0b10001001 << 8) | ClassToken<KAutoObject>));
|
||||
// static_assert(ClassToken<KLightClientSession> == ((0b00110001 << 8) | ClassToken<KAutoObject>));
|
||||
// static_assert(ClassToken<KLightServerSession> == ((0b01010001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KTransferMemory> == ((0b10010001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KTransferMemory> == ((0b01010001 << 8) | ClassToken<KAutoObject>));
|
||||
// static_assert(ClassToken<KDeviceAddressSpace> == ((0b01100001 << 8) | ClassToken<KAutoObject>));
|
||||
// static_assert(ClassToken<KSessionRequest> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KCodeMemory> == ((0b11000001 << 8) | ClassToken<KAutoObject>));
|
||||
static_assert(ClassToken<KCodeMemory> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
||||
|
||||
// Ensure that the token hierarchy reflects the class hierarchy.
|
||||
|
||||
@@ -110,7 +107,6 @@ static_assert(std::is_final_v<KPort> && std::is_base_of_v<KAutoObject, KPort>);
|
||||
static_assert(std::is_final_v<KSession> && std::is_base_of_v<KAutoObject, KSession>);
|
||||
static_assert(std::is_final_v<KSharedMemory> && std::is_base_of_v<KAutoObject, KSharedMemory>);
|
||||
static_assert(std::is_final_v<KEvent> && std::is_base_of_v<KAutoObject, KEvent>);
|
||||
static_assert(std::is_final_v<KWritableEvent> && std::is_base_of_v<KAutoObject, KWritableEvent>);
|
||||
// static_assert(std::is_final_v<KLightClientSession> &&
|
||||
// std::is_base_of_v<KAutoObject, KLightClientSession>);
|
||||
// static_assert(std::is_final_v<KLightServerSession> &&
|
||||
|
||||
@@ -101,7 +101,6 @@ public:
|
||||
KSession,
|
||||
KSharedMemory,
|
||||
KEvent,
|
||||
KWritableEvent,
|
||||
KLightClientSession,
|
||||
KLightServerSession,
|
||||
KTransferMemory,
|
||||
|
||||
@@ -21,10 +21,9 @@ void KClientSession::Destroy() {
|
||||
|
||||
void KClientSession::OnServerClosed() {}
|
||||
|
||||
Result KClientSession::SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
|
||||
Core::Timing::CoreTiming& core_timing) {
|
||||
Result KClientSession::SendSyncRequest() {
|
||||
// Signal the server session that new data is available
|
||||
return parent->GetServerSession().HandleSyncRequest(thread, memory, core_timing);
|
||||
return parent->GetServerSession().OnRequest();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -46,8 +46,7 @@ public:
|
||||
return parent;
|
||||
}
|
||||
|
||||
Result SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
|
||||
Core::Timing::CoreTiming& core_timing);
|
||||
Result SendSyncRequest();
|
||||
|
||||
void OnServerClosed();
|
||||
|
||||
|
||||
@@ -8,39 +8,45 @@
|
||||
namespace Kernel {
|
||||
|
||||
KEvent::KEvent(KernelCore& kernel_)
|
||||
: KAutoObjectWithSlabHeapAndContainer{kernel_}, readable_event{kernel_}, writable_event{
|
||||
kernel_} {}
|
||||
: KAutoObjectWithSlabHeapAndContainer{kernel_}, m_readable_event{kernel_} {}
|
||||
|
||||
KEvent::~KEvent() = default;
|
||||
|
||||
void KEvent::Initialize(std::string&& name_, KProcess* owner_) {
|
||||
// Increment reference count.
|
||||
// Because reference count is one on creation, this will result
|
||||
// in a reference count of two. Thus, when both readable and
|
||||
// writable events are closed this object will be destroyed.
|
||||
Open();
|
||||
void KEvent::Initialize(KProcess* owner) {
|
||||
// Create our readable event.
|
||||
KAutoObject::Create(std::addressof(m_readable_event));
|
||||
|
||||
// Create our sub events.
|
||||
KAutoObject::Create(std::addressof(readable_event));
|
||||
KAutoObject::Create(std::addressof(writable_event));
|
||||
|
||||
// Initialize our sub sessions.
|
||||
readable_event.Initialize(this, name_ + ":Readable");
|
||||
writable_event.Initialize(this, name_ + ":Writable");
|
||||
// Initialize our readable event.
|
||||
m_readable_event.Initialize(this);
|
||||
|
||||
// Set our owner process.
|
||||
owner = owner_;
|
||||
owner->Open();
|
||||
m_owner = owner;
|
||||
m_owner->Open();
|
||||
|
||||
// Mark initialized.
|
||||
name = std::move(name_);
|
||||
initialized = true;
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void KEvent::Finalize() {
|
||||
KAutoObjectWithSlabHeapAndContainer<KEvent, KAutoObjectWithList>::Finalize();
|
||||
}
|
||||
|
||||
Result KEvent::Signal() {
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
R_SUCCEED_IF(m_readable_event_destroyed);
|
||||
|
||||
return m_readable_event.Signal();
|
||||
}
|
||||
|
||||
Result KEvent::Clear() {
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
R_SUCCEED_IF(m_readable_event_destroyed);
|
||||
|
||||
return m_readable_event.Clear();
|
||||
}
|
||||
|
||||
void KEvent::PostDestroy(uintptr_t arg) {
|
||||
// Release the event count resource the owner process holds.
|
||||
KProcess* owner = reinterpret_cast<KProcess*>(arg);
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/k_writable_event.h"
|
||||
#include "core/hle/kernel/slab_helpers.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class KReadableEvent;
|
||||
class KWritableEvent;
|
||||
class KProcess;
|
||||
|
||||
class KEvent final : public KAutoObjectWithSlabHeapAndContainer<KEvent, KAutoObjectWithList> {
|
||||
@@ -21,37 +19,40 @@ public:
|
||||
explicit KEvent(KernelCore& kernel_);
|
||||
~KEvent() override;
|
||||
|
||||
void Initialize(std::string&& name, KProcess* owner_);
|
||||
void Initialize(KProcess* owner);
|
||||
|
||||
void Finalize() override;
|
||||
|
||||
bool IsInitialized() const override {
|
||||
return initialized;
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
uintptr_t GetPostDestroyArgument() const override {
|
||||
return reinterpret_cast<uintptr_t>(owner);
|
||||
return reinterpret_cast<uintptr_t>(m_owner);
|
||||
}
|
||||
|
||||
KProcess* GetOwner() const override {
|
||||
return owner;
|
||||
return m_owner;
|
||||
}
|
||||
|
||||
KReadableEvent& GetReadableEvent() {
|
||||
return readable_event;
|
||||
}
|
||||
|
||||
KWritableEvent& GetWritableEvent() {
|
||||
return writable_event;
|
||||
return m_readable_event;
|
||||
}
|
||||
|
||||
static void PostDestroy(uintptr_t arg);
|
||||
|
||||
Result Signal();
|
||||
Result Clear();
|
||||
|
||||
void OnReadableEventDestroyed() {
|
||||
m_readable_event_destroyed = true;
|
||||
}
|
||||
|
||||
private:
|
||||
KReadableEvent readable_event;
|
||||
KWritableEvent writable_event;
|
||||
KProcess* owner{};
|
||||
bool initialized{};
|
||||
KReadableEvent m_readable_event;
|
||||
KProcess* m_owner{};
|
||||
bool m_initialized{};
|
||||
bool m_readable_event_destroyed{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -15,31 +15,44 @@ KReadableEvent::KReadableEvent(KernelCore& kernel_) : KSynchronizationObject{ker
|
||||
|
||||
KReadableEvent::~KReadableEvent() = default;
|
||||
|
||||
bool KReadableEvent::IsSignaled() const {
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
void KReadableEvent::Initialize(KEvent* parent) {
|
||||
m_is_signaled = false;
|
||||
m_parent = parent;
|
||||
|
||||
return is_signaled;
|
||||
if (m_parent != nullptr) {
|
||||
m_parent->Open();
|
||||
}
|
||||
}
|
||||
|
||||
bool KReadableEvent::IsSignaled() const {
|
||||
ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
|
||||
|
||||
return m_is_signaled;
|
||||
}
|
||||
|
||||
void KReadableEvent::Destroy() {
|
||||
if (parent) {
|
||||
parent->Close();
|
||||
if (m_parent) {
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
m_parent->OnReadableEventDestroyed();
|
||||
}
|
||||
m_parent->Close();
|
||||
}
|
||||
}
|
||||
|
||||
Result KReadableEvent::Signal() {
|
||||
KScopedSchedulerLock lk{kernel};
|
||||
|
||||
if (!is_signaled) {
|
||||
is_signaled = true;
|
||||
NotifyAvailable();
|
||||
if (!m_is_signaled) {
|
||||
m_is_signaled = true;
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result KReadableEvent::Clear() {
|
||||
Reset();
|
||||
this->Reset();
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
@@ -47,11 +60,11 @@ Result KReadableEvent::Clear() {
|
||||
Result KReadableEvent::Reset() {
|
||||
KScopedSchedulerLock lk{kernel};
|
||||
|
||||
if (!is_signaled) {
|
||||
if (!m_is_signaled) {
|
||||
return ResultInvalidState;
|
||||
}
|
||||
|
||||
is_signaled = false;
|
||||
m_is_signaled = false;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,26 +20,23 @@ public:
|
||||
explicit KReadableEvent(KernelCore& kernel_);
|
||||
~KReadableEvent() override;
|
||||
|
||||
void Initialize(KEvent* parent_event_, std::string&& name_) {
|
||||
is_signaled = false;
|
||||
parent = parent_event_;
|
||||
name = std::move(name_);
|
||||
}
|
||||
void Initialize(KEvent* parent);
|
||||
|
||||
KEvent* GetParent() const {
|
||||
return parent;
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
Result Signal();
|
||||
Result Clear();
|
||||
|
||||
bool IsSignaled() const override;
|
||||
void Destroy() override;
|
||||
|
||||
Result Signal();
|
||||
Result Clear();
|
||||
Result Reset();
|
||||
|
||||
private:
|
||||
bool is_signaled{};
|
||||
KEvent* parent{};
|
||||
bool m_is_signaled{};
|
||||
KEvent* m_parent{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
@@ -18,13 +20,19 @@
|
||||
#include "core/hle/kernel/k_server_session.h"
|
||||
#include "core/hle/kernel/k_session.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/k_thread_queue.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/service_thread.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
|
||||
using ThreadQueueImplForKServerSessionRequest = KThreadQueue;
|
||||
|
||||
static constexpr u32 MessageBufferSize = 0x100;
|
||||
|
||||
KServerSession::KServerSession(KernelCore& kernel_)
|
||||
: KSynchronizationObject{kernel_}, m_lock{kernel_} {}
|
||||
|
||||
KServerSession::~KServerSession() = default;
|
||||
|
||||
@@ -33,17 +41,14 @@ void KServerSession::Initialize(KSession* parent_session_, std::string&& name_,
|
||||
// Set member variables.
|
||||
parent = parent_session_;
|
||||
name = std::move(name_);
|
||||
|
||||
if (manager_) {
|
||||
manager = manager_;
|
||||
} else {
|
||||
manager = std::make_shared<SessionRequestManager>(kernel);
|
||||
}
|
||||
manager = manager_;
|
||||
}
|
||||
|
||||
void KServerSession::Destroy() {
|
||||
parent->OnServerClosed();
|
||||
|
||||
this->CleanupRequests();
|
||||
|
||||
parent->Close();
|
||||
|
||||
// Release host emulation members.
|
||||
@@ -54,13 +59,13 @@ void KServerSession::Destroy() {
|
||||
}
|
||||
|
||||
void KServerSession::OnClientClosed() {
|
||||
if (manager->HasSessionHandler()) {
|
||||
if (manager && manager->HasSessionHandler()) {
|
||||
manager->SessionHandler().ClientDisconnected(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool KServerSession::IsSignaled() const {
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
|
||||
|
||||
// If the client is closed, we're always signaled.
|
||||
if (parent->IsClientClosed()) {
|
||||
@@ -68,7 +73,7 @@ bool KServerSession::IsSignaled() const {
|
||||
}
|
||||
|
||||
// Otherwise, we're signaled if we have a request and aren't handling one.
|
||||
return false;
|
||||
return !m_thread_request_list.empty() && m_current_thread_request == nullptr;
|
||||
}
|
||||
|
||||
void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) {
|
||||
@@ -173,9 +178,221 @@ Result KServerSession::CompleteSyncRequest(HLERequestContext& context) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Result KServerSession::HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
|
||||
Core::Timing::CoreTiming& core_timing) {
|
||||
return QueueSyncRequest(thread, memory);
|
||||
Result KServerSession::OnRequest() {
|
||||
// Create the wait queue.
|
||||
ThreadQueueImplForKServerSessionRequest wait_queue{kernel};
|
||||
|
||||
{
|
||||
// Lock the scheduler.
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
// Ensure that we can handle new requests.
|
||||
R_UNLESS(!parent->IsServerClosed(), ResultSessionClosed);
|
||||
|
||||
// Check that we're not terminating.
|
||||
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested);
|
||||
|
||||
if (manager) {
|
||||
// HLE request.
|
||||
auto& memory{kernel.System().Memory()};
|
||||
this->QueueSyncRequest(GetCurrentThreadPointer(kernel), memory);
|
||||
} else {
|
||||
// Non-HLE request.
|
||||
auto* thread{GetCurrentThreadPointer(kernel)};
|
||||
|
||||
// Get whether we're empty.
|
||||
const bool was_empty = m_thread_request_list.empty();
|
||||
|
||||
// Add the thread to the list.
|
||||
thread->Open();
|
||||
m_thread_request_list.push_back(thread);
|
||||
|
||||
// If we were empty, signal.
|
||||
if (was_empty) {
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a synchronous request, so we should wait for our request to complete.
|
||||
GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
|
||||
GetCurrentThread(kernel).BeginWait(&wait_queue);
|
||||
}
|
||||
|
||||
return GetCurrentThread(kernel).GetWaitResult();
|
||||
}
|
||||
|
||||
Result KServerSession::SendReply() {
|
||||
// Lock the session.
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
// Get the request.
|
||||
KThread* client_thread;
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
// Get the current request.
|
||||
client_thread = m_current_thread_request;
|
||||
R_UNLESS(client_thread != nullptr, ResultInvalidState);
|
||||
|
||||
// Clear the current request, since we're processing it.
|
||||
m_current_thread_request = nullptr;
|
||||
if (!m_thread_request_list.empty()) {
|
||||
this->NotifyAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
// Close reference to the request once we're done processing it.
|
||||
SCOPE_EXIT({ client_thread->Close(); });
|
||||
|
||||
// Extract relevant information from the request.
|
||||
// const uintptr_t client_message = request->GetAddress();
|
||||
// const size_t client_buffer_size = request->GetSize();
|
||||
// KThread *client_thread = request->GetThread();
|
||||
// KEvent *event = request->GetEvent();
|
||||
|
||||
// Check whether we're closed.
|
||||
const bool closed = (client_thread == nullptr || parent->IsClientClosed());
|
||||
|
||||
Result result = ResultSuccess;
|
||||
if (!closed) {
|
||||
// If we're not closed, send the reply.
|
||||
Core::Memory::Memory& memory{kernel.System().Memory()};
|
||||
KThread* server_thread{GetCurrentThreadPointer(kernel)};
|
||||
UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
|
||||
|
||||
auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
|
||||
auto* dst_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress());
|
||||
std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize);
|
||||
} else {
|
||||
result = ResultSessionClosed;
|
||||
}
|
||||
|
||||
// Select a result for the client.
|
||||
Result client_result = result;
|
||||
if (closed && R_SUCCEEDED(result)) {
|
||||
result = ResultSessionClosed;
|
||||
client_result = ResultSessionClosed;
|
||||
} else {
|
||||
result = ResultSuccess;
|
||||
}
|
||||
|
||||
// If there's a client thread, update it.
|
||||
if (client_thread != nullptr) {
|
||||
// End the client thread's wait.
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
if (!client_thread->IsTerminationRequested()) {
|
||||
client_thread->EndWait(client_result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result KServerSession::ReceiveRequest() {
|
||||
// Lock the session.
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
// Get the request and client thread.
|
||||
// KSessionRequest *request;
|
||||
KThread* client_thread;
|
||||
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
// Ensure that we can service the request.
|
||||
R_UNLESS(!parent->IsClientClosed(), ResultSessionClosed);
|
||||
|
||||
// Ensure we aren't already servicing a request.
|
||||
R_UNLESS(m_current_thread_request == nullptr, ResultNotFound);
|
||||
|
||||
// Ensure we have a request to service.
|
||||
R_UNLESS(!m_thread_request_list.empty(), ResultNotFound);
|
||||
|
||||
// Pop the first request from the list.
|
||||
client_thread = m_thread_request_list.front();
|
||||
m_thread_request_list.pop_front();
|
||||
|
||||
// Get the thread for the request.
|
||||
R_UNLESS(client_thread != nullptr, ResultSessionClosed);
|
||||
|
||||
// Open the client thread.
|
||||
client_thread->Open();
|
||||
}
|
||||
|
||||
// SCOPE_EXIT({ client_thread->Close(); });
|
||||
|
||||
// Set the request as our current.
|
||||
m_current_thread_request = client_thread;
|
||||
|
||||
// Receive the message.
|
||||
Core::Memory::Memory& memory{kernel.System().Memory()};
|
||||
KThread* server_thread{GetCurrentThreadPointer(kernel)};
|
||||
UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
|
||||
|
||||
auto* src_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress());
|
||||
auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
|
||||
std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize);
|
||||
|
||||
// We succeeded.
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void KServerSession::CleanupRequests() {
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
// Clean up any pending requests.
|
||||
while (true) {
|
||||
// Get the next request.
|
||||
// KSessionRequest *request = nullptr;
|
||||
KThread* client_thread = nullptr;
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
if (m_current_thread_request) {
|
||||
// Choose the current request if we have one.
|
||||
client_thread = m_current_thread_request;
|
||||
m_current_thread_request = nullptr;
|
||||
} else if (!m_thread_request_list.empty()) {
|
||||
// Pop the request from the front of the list.
|
||||
client_thread = m_thread_request_list.front();
|
||||
m_thread_request_list.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no request, we're done.
|
||||
if (client_thread == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Close a reference to the request once it's cleaned up.
|
||||
SCOPE_EXIT({ client_thread->Close(); });
|
||||
|
||||
// Extract relevant information from the request.
|
||||
// const uintptr_t client_message = request->GetAddress();
|
||||
// const size_t client_buffer_size = request->GetSize();
|
||||
// KThread *client_thread = request->GetThread();
|
||||
// KEvent *event = request->GetEvent();
|
||||
|
||||
// KProcess *server_process = request->GetServerProcess();
|
||||
// KProcess *client_process = (client_thread != nullptr) ?
|
||||
// client_thread->GetOwnerProcess() : nullptr;
|
||||
// KProcessPageTable *client_page_table = (client_process != nullptr) ?
|
||||
// &client_process->GetPageTable() : nullptr;
|
||||
|
||||
// Cleanup the mappings.
|
||||
// Result result = CleanupMap(request, server_process, client_page_table);
|
||||
|
||||
// If there's a client thread, update it.
|
||||
if (client_thread != nullptr) {
|
||||
// End the client thread's wait.
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
if (!client_thread->IsTerminationRequested()) {
|
||||
client_thread->EndWait(ResultSessionClosed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/k_light_lock.h"
|
||||
#include "core/hle/kernel/k_synchronization_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
@@ -59,25 +61,15 @@ public:
|
||||
void OnClientClosed();
|
||||
|
||||
void ClientConnected(SessionRequestHandlerPtr handler) {
|
||||
manager->SetSessionHandler(std::move(handler));
|
||||
if (manager) {
|
||||
manager->SetSessionHandler(std::move(handler));
|
||||
}
|
||||
}
|
||||
|
||||
void ClientDisconnected() {
|
||||
manager = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a sync request from the emulated application.
|
||||
*
|
||||
* @param thread Thread that initiated the request.
|
||||
* @param memory Memory context to handle the sync request under.
|
||||
* @param core_timing Core timing context to schedule the request event under.
|
||||
*
|
||||
* @returns Result from the operation.
|
||||
*/
|
||||
Result HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
|
||||
Core::Timing::CoreTiming& core_timing);
|
||||
|
||||
/// Adds a new domain request handler to the collection of request handlers within
|
||||
/// this ServerSession instance.
|
||||
void AppendDomainHandler(SessionRequestHandlerPtr handler);
|
||||
@@ -88,7 +80,7 @@ public:
|
||||
|
||||
/// Returns true if the session has been converted to a domain, otherwise False
|
||||
bool IsDomain() const {
|
||||
return manager->IsDomain();
|
||||
return manager && manager->IsDomain();
|
||||
}
|
||||
|
||||
/// Converts the session to a domain at the end of the current command
|
||||
@@ -101,7 +93,15 @@ public:
|
||||
return manager;
|
||||
}
|
||||
|
||||
/// TODO: flesh these out to match the real kernel
|
||||
Result OnRequest();
|
||||
Result SendReply();
|
||||
Result ReceiveRequest();
|
||||
|
||||
private:
|
||||
/// Frees up waiting client sessions when this server session is about to die
|
||||
void CleanupRequests();
|
||||
|
||||
/// Queues a sync request from the emulated application.
|
||||
Result QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
|
||||
|
||||
@@ -112,7 +112,7 @@ private:
|
||||
/// object handle.
|
||||
Result HandleDomainSyncRequest(Kernel::HLERequestContext& context);
|
||||
|
||||
/// This session's HLE request handlers
|
||||
/// This session's HLE request handlers; if nullptr, this is not an HLE server
|
||||
std::shared_ptr<SessionRequestManager> manager;
|
||||
|
||||
/// When set to True, converts the session to a domain at the end of the command
|
||||
@@ -120,6 +120,13 @@ private:
|
||||
|
||||
/// KSession that owns this KServerSession
|
||||
KSession* parent{};
|
||||
|
||||
/// List of threads which are pending a reply.
|
||||
/// FIXME: KSessionRequest
|
||||
std::list<KThread*> m_thread_request_list;
|
||||
KThread* m_current_thread_request{};
|
||||
|
||||
KLightLock m_lock;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -23,7 +23,7 @@ void KWorkerTask::DoWorkerTask() {
|
||||
}
|
||||
}
|
||||
|
||||
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "yuzu:KWorkerTaskManager") {}
|
||||
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {}
|
||||
|
||||
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
|
||||
ASSERT(type <= WorkerType::Count);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/k_writable_event.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
KWritableEvent::KWritableEvent(KernelCore& kernel_)
|
||||
: KAutoObjectWithSlabHeapAndContainer{kernel_} {}
|
||||
|
||||
KWritableEvent::~KWritableEvent() = default;
|
||||
|
||||
void KWritableEvent::Initialize(KEvent* parent_event_, std::string&& name_) {
|
||||
parent = parent_event_;
|
||||
name = std::move(name_);
|
||||
parent->GetReadableEvent().Open();
|
||||
}
|
||||
|
||||
Result KWritableEvent::Signal() {
|
||||
return parent->GetReadableEvent().Signal();
|
||||
}
|
||||
|
||||
Result KWritableEvent::Clear() {
|
||||
return parent->GetReadableEvent().Clear();
|
||||
}
|
||||
|
||||
void KWritableEvent::Destroy() {
|
||||
// Close our references.
|
||||
parent->GetReadableEvent().Close();
|
||||
parent->Close();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/kernel/k_auto_object.h"
|
||||
#include "core/hle/kernel/slab_helpers.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class KEvent;
|
||||
|
||||
class KWritableEvent final
|
||||
: public KAutoObjectWithSlabHeapAndContainer<KWritableEvent, KAutoObjectWithList> {
|
||||
KERNEL_AUTOOBJECT_TRAITS(KWritableEvent, KAutoObject);
|
||||
|
||||
public:
|
||||
explicit KWritableEvent(KernelCore& kernel_);
|
||||
~KWritableEvent() override;
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
|
||||
|
||||
void Initialize(KEvent* parent_, std::string&& name_);
|
||||
Result Signal();
|
||||
Result Clear();
|
||||
|
||||
KEvent* GetParent() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
private:
|
||||
KEvent* parent{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -48,7 +48,7 @@ namespace Kernel {
|
||||
struct KernelCore::Impl {
|
||||
explicit Impl(Core::System& system_, KernelCore& kernel_)
|
||||
: time_manager{system_},
|
||||
service_threads_manager{1, "yuzu:ServiceThreadsManager"}, system{system_} {}
|
||||
service_threads_manager{1, "ServiceThreadsManager"}, system{system_} {}
|
||||
|
||||
void SetMulticore(bool is_multi) {
|
||||
is_multicore = is_multi;
|
||||
|
||||
@@ -52,7 +52,6 @@ class KThread;
|
||||
class KThreadLocalPage;
|
||||
class KTransferMemory;
|
||||
class KWorkerTaskManager;
|
||||
class KWritableEvent;
|
||||
class KCodeMemory;
|
||||
class PhysicalCore;
|
||||
class ServiceThread;
|
||||
@@ -345,8 +344,6 @@ public:
|
||||
return slab_heap_container->thread;
|
||||
} else if constexpr (std::is_same_v<T, KTransferMemory>) {
|
||||
return slab_heap_container->transfer_memory;
|
||||
} else if constexpr (std::is_same_v<T, KWritableEvent>) {
|
||||
return slab_heap_container->writeable_event;
|
||||
} else if constexpr (std::is_same_v<T, KCodeMemory>) {
|
||||
return slab_heap_container->code_memory;
|
||||
} else if constexpr (std::is_same_v<T, KPageBuffer>) {
|
||||
@@ -412,7 +409,6 @@ private:
|
||||
KSlabHeap<KSharedMemoryInfo> shared_memory_info;
|
||||
KSlabHeap<KThread> thread;
|
||||
KSlabHeap<KTransferMemory> transfer_memory;
|
||||
KSlabHeap<KWritableEvent> writeable_event;
|
||||
KSlabHeap<KCodeMemory> code_memory;
|
||||
KSlabHeap<KPageBuffer> page_buffer;
|
||||
KSlabHeap<KThreadLocalPage> thread_local_page;
|
||||
|
||||
@@ -36,7 +36,7 @@ ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std
|
||||
: service_name{name} {
|
||||
for (std::size_t i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back([this, &kernel](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(std::string{"yuzu:HleService:" + service_name}.c_str());
|
||||
Common::SetCurrentThreadName(std::string{service_name}.c_str());
|
||||
|
||||
// Wait for first request before trying to acquire a render context
|
||||
{
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
#include "core/hle/kernel/k_resource_limit.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_resource_reservation.h"
|
||||
#include "core/hle/kernel/k_session.h"
|
||||
#include "core/hle/kernel/k_shared_memory.h"
|
||||
#include "core/hle/kernel/k_synchronization_object.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/k_thread_queue.h"
|
||||
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
#include "core/hle/kernel/k_writable_event.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -256,6 +256,93 @@ static Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u3
|
||||
return UnmapMemory(system, dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u64 name) {
|
||||
auto& process = *system.CurrentProcess();
|
||||
auto& handle_table = process.GetHandleTable();
|
||||
|
||||
// Declare the session we're going to allocate.
|
||||
T* session;
|
||||
|
||||
// Reserve a new session from the process resource limit.
|
||||
// FIXME: LimitableResource_SessionCountMax
|
||||
KScopedResourceReservation session_reservation(&process, LimitableResource::Sessions);
|
||||
if (session_reservation.Succeeded()) {
|
||||
session = T::Create(system.Kernel());
|
||||
} else {
|
||||
return ResultLimitReached;
|
||||
|
||||
// // We couldn't reserve a session. Check that we support dynamically expanding the
|
||||
// // resource limit.
|
||||
// R_UNLESS(process.GetResourceLimit() ==
|
||||
// &system.Kernel().GetSystemResourceLimit(), ResultLimitReached);
|
||||
// R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), ResultLimitReached());
|
||||
|
||||
// // Try to allocate a session from unused slab memory.
|
||||
// session = T::CreateFromUnusedSlabMemory();
|
||||
// R_UNLESS(session != nullptr, ResultLimitReached);
|
||||
// ON_RESULT_FAILURE { session->Close(); };
|
||||
|
||||
// // If we're creating a KSession, we want to add two KSessionRequests to the heap, to
|
||||
// // prevent request exhaustion.
|
||||
// // NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's
|
||||
// // no reason to not do this statically.
|
||||
// if constexpr (std::same_as<T, KSession>) {
|
||||
// for (size_t i = 0; i < 2; i++) {
|
||||
// KSessionRequest* request = KSessionRequest::CreateFromUnusedSlabMemory();
|
||||
// R_UNLESS(request != nullptr, ResultLimitReached);
|
||||
// request->Close();
|
||||
// }
|
||||
// }
|
||||
|
||||
// We successfully allocated a session, so add the object we allocated to the resource
|
||||
// limit.
|
||||
// system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::Sessions, 1);
|
||||
}
|
||||
|
||||
// Check that we successfully created a session.
|
||||
R_UNLESS(session != nullptr, ResultOutOfResource);
|
||||
|
||||
// Initialize the session.
|
||||
session->Initialize(nullptr, fmt::format("{}", name));
|
||||
|
||||
// Commit the session reservation.
|
||||
session_reservation.Commit();
|
||||
|
||||
// Ensure that we clean up the session (and its only references are handle table) on function
|
||||
// end.
|
||||
SCOPE_EXIT({
|
||||
session->GetClientSession().Close();
|
||||
session->GetServerSession().Close();
|
||||
});
|
||||
|
||||
// Register the session.
|
||||
T::Register(system.Kernel(), session);
|
||||
|
||||
// Add the server session to the handle table.
|
||||
R_TRY(handle_table.Add(out_server, &session->GetServerSession()));
|
||||
|
||||
// Add the client session to the handle table.
|
||||
const auto result = handle_table.Add(out_client, &session->GetClientSession());
|
||||
|
||||
if (!R_SUCCEEDED(result)) {
|
||||
// Ensure that we maintaing a clean handle state on exit.
|
||||
handle_table.Remove(*out_server);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client,
|
||||
u32 is_light, u64 name) {
|
||||
if (is_light) {
|
||||
// return CreateSession<KLightSession>(system, out_server, out_client, name);
|
||||
return ResultUnknown;
|
||||
} else {
|
||||
return CreateSession<KSession>(system, out_server, out_client, name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to an OS service given the port name, returns the handle to the port to out
|
||||
static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) {
|
||||
auto& memory = system.Memory();
|
||||
@@ -295,7 +382,8 @@ static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_n
|
||||
|
||||
// Create a session.
|
||||
KClientSession* session{};
|
||||
R_TRY(port->CreateSession(std::addressof(session)));
|
||||
R_TRY(port->CreateSession(std::addressof(session),
|
||||
std::make_shared<SessionRequestManager>(kernel)));
|
||||
port->Close();
|
||||
|
||||
// Register the session in the table, close the extra reference.
|
||||
@@ -313,7 +401,7 @@ static Result ConnectToNamedPort32(Core::System& system, Handle* out_handle,
|
||||
return ConnectToNamedPort(system, out_handle, port_name_address);
|
||||
}
|
||||
|
||||
/// Makes a blocking IPC call to an OS service.
|
||||
/// Makes a blocking IPC call to a service.
|
||||
static Result SendSyncRequest(Core::System& system, Handle handle) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
@@ -327,22 +415,75 @@ static Result SendSyncRequest(Core::System& system, Handle handle) {
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
|
||||
|
||||
{
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
// This is a synchronous request, so we should wait for our request to complete.
|
||||
GetCurrentThread(kernel).BeginWait(std::addressof(wait_queue));
|
||||
GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
|
||||
session->SendSyncRequest(&GetCurrentThread(kernel), system.Memory(), system.CoreTiming());
|
||||
}
|
||||
|
||||
return GetCurrentThread(kernel).GetWaitResult();
|
||||
return session->SendSyncRequest();
|
||||
}
|
||||
|
||||
static Result SendSyncRequest32(Core::System& system, Handle handle) {
|
||||
return SendSyncRequest(system, handle);
|
||||
}
|
||||
|
||||
static Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles,
|
||||
s32 num_handles, Handle reply_target, s64 timeout_ns) {
|
||||
auto& kernel = system.Kernel();
|
||||
auto& handle_table = GetCurrentThread(kernel).GetOwnerProcess()->GetHandleTable();
|
||||
|
||||
// Convert handle list to object table.
|
||||
std::vector<KSynchronizationObject*> objs(num_handles);
|
||||
R_UNLESS(
|
||||
handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, num_handles),
|
||||
ResultInvalidHandle);
|
||||
|
||||
// Ensure handles are closed when we're done.
|
||||
SCOPE_EXIT({
|
||||
for (auto i = 0; i < num_handles; ++i) {
|
||||
objs[i]->Close();
|
||||
}
|
||||
});
|
||||
|
||||
// Reply to the target, if one is specified.
|
||||
if (reply_target != InvalidHandle) {
|
||||
KScopedAutoObject session = handle_table.GetObject<KServerSession>(reply_target);
|
||||
R_UNLESS(session.IsNotNull(), ResultInvalidHandle);
|
||||
|
||||
// If we fail to reply, we want to set the output index to -1.
|
||||
// ON_RESULT_FAILURE { *out_index = -1; };
|
||||
|
||||
// Send the reply.
|
||||
// R_TRY(session->SendReply());
|
||||
|
||||
Result rc = session->SendReply();
|
||||
if (!R_SUCCEEDED(rc)) {
|
||||
*out_index = -1;
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for a message.
|
||||
while (true) {
|
||||
// Wait for an object.
|
||||
s32 index;
|
||||
Result result = KSynchronizationObject::Wait(kernel, &index, objs.data(),
|
||||
static_cast<s32>(objs.size()), timeout_ns);
|
||||
if (result == ResultTimedOut) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Receive the request.
|
||||
if (R_SUCCEEDED(result)) {
|
||||
KServerSession* session = objs[index]->DynamicCast<KServerSession*>();
|
||||
if (session != nullptr) {
|
||||
result = session->ReceiveRequest();
|
||||
if (result == ResultNotFound) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*out_index = index;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID for the specified thread.
|
||||
static Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) {
|
||||
// Get the thread from its handle.
|
||||
@@ -2303,11 +2444,11 @@ static Result SignalEvent(Core::System& system, Handle event_handle) {
|
||||
// Get the current handle table.
|
||||
const KHandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||
|
||||
// Get the writable event.
|
||||
KScopedAutoObject writable_event = handle_table.GetObject<KWritableEvent>(event_handle);
|
||||
R_UNLESS(writable_event.IsNotNull(), ResultInvalidHandle);
|
||||
// Get the event.
|
||||
KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle);
|
||||
R_UNLESS(event.IsNotNull(), ResultInvalidHandle);
|
||||
|
||||
return writable_event->Signal();
|
||||
return event->Signal();
|
||||
}
|
||||
|
||||
static Result SignalEvent32(Core::System& system, Handle event_handle) {
|
||||
@@ -2322,9 +2463,9 @@ static Result ClearEvent(Core::System& system, Handle event_handle) {
|
||||
|
||||
// Try to clear the writable event.
|
||||
{
|
||||
KScopedAutoObject writable_event = handle_table.GetObject<KWritableEvent>(event_handle);
|
||||
if (writable_event.IsNotNull()) {
|
||||
return writable_event->Clear();
|
||||
KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle);
|
||||
if (event.IsNotNull()) {
|
||||
return event->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2362,24 +2503,24 @@ static Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_r
|
||||
R_UNLESS(event != nullptr, ResultOutOfResource);
|
||||
|
||||
// Initialize the event.
|
||||
event->Initialize("CreateEvent", kernel.CurrentProcess());
|
||||
event->Initialize(kernel.CurrentProcess());
|
||||
|
||||
// Commit the thread reservation.
|
||||
event_reservation.Commit();
|
||||
|
||||
// Ensure that we clean up the event (and its only references are handle table) on function end.
|
||||
SCOPE_EXIT({
|
||||
event->GetWritableEvent().Close();
|
||||
event->GetReadableEvent().Close();
|
||||
event->Close();
|
||||
});
|
||||
|
||||
// Register the event.
|
||||
KEvent::Register(kernel, event);
|
||||
|
||||
// Add the writable event to the handle table.
|
||||
R_TRY(handle_table.Add(out_write, std::addressof(event->GetWritableEvent())));
|
||||
// Add the event to the handle table.
|
||||
R_TRY(handle_table.Add(out_write, event));
|
||||
|
||||
// Add the writable event to the handle table.
|
||||
// Ensure that we maintaing a clean handle state on exit.
|
||||
auto handle_guard = SCOPE_GUARD({ handle_table.Remove(*out_write); });
|
||||
|
||||
// Add the readable event to the handle table.
|
||||
@@ -2860,10 +3001,10 @@ static const FunctionDef SVC_Table_64[] = {
|
||||
{0x3D, SvcWrap64<ChangeKernelTraceState>, "ChangeKernelTraceState"},
|
||||
{0x3E, nullptr, "Unknown3e"},
|
||||
{0x3F, nullptr, "Unknown3f"},
|
||||
{0x40, nullptr, "CreateSession"},
|
||||
{0x40, SvcWrap64<CreateSession>, "CreateSession"},
|
||||
{0x41, nullptr, "AcceptSession"},
|
||||
{0x42, nullptr, "ReplyAndReceiveLight"},
|
||||
{0x43, nullptr, "ReplyAndReceive"},
|
||||
{0x43, SvcWrap64<ReplyAndReceive>, "ReplyAndReceive"},
|
||||
{0x44, nullptr, "ReplyAndReceiveWithUserBuffer"},
|
||||
{0x45, SvcWrap64<CreateEvent>, "CreateEvent"},
|
||||
{0x46, nullptr, "MapIoRegion"},
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/svc_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -346,6 +347,37 @@ void SvcWrap64(Core::System& system) {
|
||||
FuncReturn(system, retval);
|
||||
}
|
||||
|
||||
// Used by CreateSession
|
||||
template <Result func(Core::System&, Handle*, Handle*, u32, u64)>
|
||||
void SvcWrap64(Core::System& system) {
|
||||
Handle param_1 = 0;
|
||||
Handle param_2 = 0;
|
||||
const u32 retval = func(system, ¶m_1, ¶m_2, static_cast<u32>(Param(system, 2)),
|
||||
static_cast<u32>(Param(system, 3)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
system.CurrentArmInterface().SetReg(2, param_2);
|
||||
FuncReturn(system, retval);
|
||||
}
|
||||
|
||||
// Used by ReplyAndReceive
|
||||
template <Result func(Core::System&, s32*, Handle*, s32, Handle, s64)>
|
||||
void SvcWrap64(Core::System& system) {
|
||||
s32 param_1 = 0;
|
||||
s32 num_handles = static_cast<s32>(Param(system, 2));
|
||||
|
||||
std::vector<Handle> handles(num_handles);
|
||||
system.Memory().ReadBlock(Param(system, 1), handles.data(), num_handles * sizeof(Handle));
|
||||
|
||||
const u32 retval = func(system, ¶m_1, handles.data(), num_handles,
|
||||
static_cast<s32>(Param(system, 3)), static_cast<s64>(Param(system, 4)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
}
|
||||
|
||||
// Used by WaitForAddress
|
||||
template <Result func(Core::System&, u64, Svc::ArbitrationType, s32, s64)>
|
||||
void SvcWrap64(Core::System& system) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/expected.h"
|
||||
|
||||
@@ -130,6 +131,10 @@ union Result {
|
||||
[[nodiscard]] constexpr bool IsError() const {
|
||||
return !IsSuccess();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsFailure() const {
|
||||
return !IsSuccess();
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<Result>);
|
||||
|
||||
@@ -349,10 +354,109 @@ private:
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define R_SUCCEEDED(res) (res.IsSuccess())
|
||||
#define R_SUCCEEDED(res) (static_cast<Result>(res).IsSuccess())
|
||||
#define R_FAILED(res) (static_cast<Result>(res).IsFailure())
|
||||
|
||||
/// Evaluates a boolean expression, and succeeds if that expression is true.
|
||||
#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), ResultSuccess)
|
||||
namespace ResultImpl {
|
||||
template <auto EvaluateResult, class F>
|
||||
class ScopedResultGuard {
|
||||
YUZU_NON_COPYABLE(ScopedResultGuard);
|
||||
YUZU_NON_MOVEABLE(ScopedResultGuard);
|
||||
|
||||
private:
|
||||
Result& m_ref;
|
||||
F m_f;
|
||||
|
||||
public:
|
||||
constexpr ScopedResultGuard(Result& ref, F f) : m_ref(ref), m_f(std::move(f)) {}
|
||||
constexpr ~ScopedResultGuard() {
|
||||
if (EvaluateResult(m_ref)) {
|
||||
m_f();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <auto EvaluateResult>
|
||||
class ResultReferenceForScopedResultGuard {
|
||||
private:
|
||||
Result& m_ref;
|
||||
|
||||
public:
|
||||
constexpr ResultReferenceForScopedResultGuard(Result& r) : m_ref(r) {}
|
||||
constexpr operator Result&() const {
|
||||
return m_ref;
|
||||
}
|
||||
};
|
||||
|
||||
template <auto EvaluateResult, typename F>
|
||||
constexpr ScopedResultGuard<EvaluateResult, F> operator+(
|
||||
ResultReferenceForScopedResultGuard<EvaluateResult> ref, F&& f) {
|
||||
return ScopedResultGuard<EvaluateResult, F>(static_cast<Result&>(ref), std::forward<F>(f));
|
||||
}
|
||||
|
||||
constexpr bool EvaluateResultSuccess(const Result& r) {
|
||||
return R_SUCCEEDED(r);
|
||||
}
|
||||
constexpr bool EvaluateResultFailure(const Result& r) {
|
||||
return R_FAILED(r);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr void UpdateCurrentResultReference(T result_reference, Result result) = delete;
|
||||
// Intentionally not defined
|
||||
|
||||
template <>
|
||||
constexpr void UpdateCurrentResultReference<Result&>(Result& result_reference, Result result) {
|
||||
result_reference = result;
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr void UpdateCurrentResultReference<const Result>(Result result_reference, Result result) {}
|
||||
} // namespace ResultImpl
|
||||
|
||||
#define DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(COUNTER_VALUE) \
|
||||
[[maybe_unused]] constexpr bool HasPrevRef_##COUNTER_VALUE = \
|
||||
std::same_as<decltype(__TmpCurrentResultReference), Result&>; \
|
||||
[[maybe_unused]] auto& PrevRef_##COUNTER_VALUE = __TmpCurrentResultReference; \
|
||||
[[maybe_unused]] Result __tmp_result_##COUNTER_VALUE = ResultSuccess; \
|
||||
Result& __TmpCurrentResultReference = \
|
||||
HasPrevRef_##COUNTER_VALUE ? PrevRef_##COUNTER_VALUE : __tmp_result_##COUNTER_VALUE
|
||||
|
||||
#define ON_RESULT_RETURN_IMPL(...) \
|
||||
static_assert(std::same_as<decltype(__TmpCurrentResultReference), Result&>); \
|
||||
auto RESULT_GUARD_STATE_##__COUNTER__ = \
|
||||
ResultImpl::ResultReferenceForScopedResultGuard<__VA_ARGS__>( \
|
||||
__TmpCurrentResultReference) + \
|
||||
[&]()
|
||||
|
||||
#define ON_RESULT_FAILURE_2 ON_RESULT_RETURN_IMPL(ResultImpl::EvaluateResultFailure)
|
||||
|
||||
#define ON_RESULT_FAILURE \
|
||||
DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \
|
||||
ON_RESULT_FAILURE_2
|
||||
|
||||
#define ON_RESULT_SUCCESS_2 ON_RESULT_RETURN_IMPL(ResultImpl::EvaluateResultSuccess)
|
||||
|
||||
#define ON_RESULT_SUCCESS \
|
||||
DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \
|
||||
ON_RESULT_SUCCESS_2
|
||||
|
||||
constexpr inline Result __TmpCurrentResultReference = ResultSuccess;
|
||||
|
||||
/// Returns a result.
|
||||
#define R_RETURN(res_expr) \
|
||||
{ \
|
||||
const Result _tmp_r_throw_rc = (res_expr); \
|
||||
ResultImpl::UpdateCurrentResultReference<decltype(__TmpCurrentResultReference)>( \
|
||||
__TmpCurrentResultReference, _tmp_r_throw_rc); \
|
||||
return _tmp_r_throw_rc; \
|
||||
}
|
||||
|
||||
/// Returns ResultSuccess()
|
||||
#define R_SUCCEED() R_RETURN(ResultSuccess)
|
||||
|
||||
/// Throws a result.
|
||||
#define R_THROW(res_expr) R_RETURN(res_expr)
|
||||
|
||||
/// Evaluates a boolean expression, and returns a result unless that expression is true.
|
||||
#define R_UNLESS(expr, res) \
|
||||
@@ -361,7 +465,7 @@ private:
|
||||
if (res.IsError()) { \
|
||||
LOG_ERROR(Kernel, "Failed with result: {}", res.raw); \
|
||||
} \
|
||||
return res; \
|
||||
R_THROW(res); \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -369,7 +473,10 @@ private:
|
||||
#define R_TRY(res_expr) \
|
||||
{ \
|
||||
const auto _tmp_r_try_rc = (res_expr); \
|
||||
if (_tmp_r_try_rc.IsError()) { \
|
||||
return _tmp_r_try_rc; \
|
||||
if (R_FAILED(_tmp_r_try_rc)) { \
|
||||
R_THROW(_tmp_r_try_rc); \
|
||||
} \
|
||||
}
|
||||
|
||||
/// Evaluates a boolean expression, and succeeds if that expression is true.
|
||||
#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), ResultSuccess)
|
||||
|
||||
@@ -64,7 +64,7 @@ void IAsyncContext::GetResult(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void IAsyncContext::MarkComplete() {
|
||||
is_complete.store(true);
|
||||
completion_event->GetWritableEvent().Signal();
|
||||
completion_event->Signal();
|
||||
}
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -316,7 +316,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv
|
||||
|
||||
accumulated_suspended_tick_changed_event =
|
||||
service_context.CreateEvent("ISelfController:AccumulatedSuspendedTickChangedEvent");
|
||||
accumulated_suspended_tick_changed_event->GetWritableEvent().Signal();
|
||||
accumulated_suspended_tick_changed_event->Signal();
|
||||
}
|
||||
|
||||
ISelfController::~ISelfController() {
|
||||
@@ -378,7 +378,7 @@ void ISelfController::LeaveFatalSection(Kernel::HLERequestContext& ctx) {
|
||||
void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
launchable_event->GetWritableEvent().Signal();
|
||||
launchable_event->Signal();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
@@ -618,18 +618,18 @@ Kernel::KReadableEvent& AppletMessageQueue::GetOperationModeChangedEvent() {
|
||||
|
||||
void AppletMessageQueue::PushMessage(AppletMessage msg) {
|
||||
messages.push(msg);
|
||||
on_new_message->GetWritableEvent().Signal();
|
||||
on_new_message->Signal();
|
||||
}
|
||||
|
||||
AppletMessageQueue::AppletMessage AppletMessageQueue::PopMessage() {
|
||||
if (messages.empty()) {
|
||||
on_new_message->GetWritableEvent().Clear();
|
||||
on_new_message->Clear();
|
||||
return AppletMessage::None;
|
||||
}
|
||||
auto msg = messages.front();
|
||||
messages.pop();
|
||||
if (messages.empty()) {
|
||||
on_new_message->GetWritableEvent().Clear();
|
||||
on_new_message->Clear();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
@@ -653,7 +653,7 @@ void AppletMessageQueue::FocusStateChanged() {
|
||||
void AppletMessageQueue::OperationModeChanged() {
|
||||
PushMessage(AppletMessage::OperationModeChanged);
|
||||
PushMessage(AppletMessage::PerformanceModeChanged);
|
||||
on_operation_mode_changed->GetWritableEvent().Signal();
|
||||
on_operation_mode_changed->Signal();
|
||||
}
|
||||
|
||||
ICommonStateGetter::ICommonStateGetter(Core::System& system_,
|
||||
|
||||
@@ -65,7 +65,7 @@ std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
|
||||
|
||||
auto out = std::move(out_channel.front());
|
||||
out_channel.pop_front();
|
||||
pop_out_data_event->GetWritableEvent().Clear();
|
||||
pop_out_data_event->Clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
|
||||
|
||||
auto out = std::move(out_interactive_channel.front());
|
||||
out_interactive_channel.pop_front();
|
||||
pop_interactive_out_data_event->GetWritableEvent().Clear();
|
||||
pop_interactive_out_data_event->Clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ void AppletDataBroker::PushNormalDataFromGame(std::shared_ptr<IStorage>&& storag
|
||||
|
||||
void AppletDataBroker::PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage) {
|
||||
out_channel.emplace_back(std::move(storage));
|
||||
pop_out_data_event->GetWritableEvent().Signal();
|
||||
pop_out_data_event->Signal();
|
||||
}
|
||||
|
||||
void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage) {
|
||||
@@ -112,11 +112,11 @@ void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& s
|
||||
|
||||
void AppletDataBroker::PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage) {
|
||||
out_interactive_channel.emplace_back(std::move(storage));
|
||||
pop_interactive_out_data_event->GetWritableEvent().Signal();
|
||||
pop_interactive_out_data_event->Signal();
|
||||
}
|
||||
|
||||
void AppletDataBroker::SignalStateChanged() {
|
||||
state_changed_event->GetWritableEvent().Signal();
|
||||
state_changed_event->Signal();
|
||||
|
||||
switch (applet_mode) {
|
||||
case LibraryAppletMode::AllForeground:
|
||||
|
||||
@@ -239,7 +239,7 @@ public:
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
event->GetWritableEvent().Signal();
|
||||
event->Signal();
|
||||
}
|
||||
|
||||
~IAudioDevice() override {
|
||||
@@ -325,7 +325,7 @@ private:
|
||||
void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "(STUBBED) called");
|
||||
|
||||
event->GetWritableEvent().Signal();
|
||||
event->Signal();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
@@ -82,7 +82,7 @@ void ProgressServiceBackend::FinishDownload(Result result) {
|
||||
}
|
||||
|
||||
void ProgressServiceBackend::SignalUpdate() {
|
||||
update_event->GetWritableEvent().Signal();
|
||||
update_event->Signal();
|
||||
}
|
||||
|
||||
Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
|
||||
|
||||
@@ -707,7 +707,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
{31, nullptr, "OpenGameCardFileSystem"},
|
||||
{32, nullptr, "ExtendSaveDataFileSystem"},
|
||||
{33, nullptr, "DeleteCacheStorage"},
|
||||
{34, nullptr, "GetCacheStorageSize"},
|
||||
{34, &FSP_SRV::GetCacheStorageSize, "GetCacheStorageSize"},
|
||||
{35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
|
||||
{36, nullptr, "OpenHostFileSystemWithOption"},
|
||||
{51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
|
||||
@@ -1107,6 +1107,18 @@ void FSP_SRV::GetProgramIndexForAccessLog(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(access_log_program_index);
|
||||
}
|
||||
|
||||
void FSP_SRV::GetCacheStorageSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto index{rp.Pop<s32>()};
|
||||
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called with index={}", index);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(s64{0});
|
||||
rb.Push(s64{0});
|
||||
}
|
||||
|
||||
class IMultiCommitManager final : public ServiceFramework<IMultiCommitManager> {
|
||||
public:
|
||||
explicit IMultiCommitManager(Core::System& system_)
|
||||
|
||||
@@ -54,6 +54,7 @@ private:
|
||||
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
|
||||
void GetProgramIndexForAccessLog(Kernel::HLERequestContext& ctx);
|
||||
void OpenMultiCommitManager(Kernel::HLERequestContext& ctx);
|
||||
void GetCacheStorageSize(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSystemController& fsc;
|
||||
const FileSys::ContentProvider& content_provider;
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
{10101, &IFriendService::GetFriendList, "GetFriendList"},
|
||||
{10102, nullptr, "UpdateFriendInfo"},
|
||||
{10110, nullptr, "GetFriendProfileImage"},
|
||||
{10120, nullptr, "IsFriendListCacheAvailable"},
|
||||
{10120, &IFriendService::CheckFriendListAvailability, "CheckFriendListAvailability"},
|
||||
{10121, nullptr, "EnsureFriendListAvailable"},
|
||||
{10200, nullptr, "SendFriendRequestForApplication"},
|
||||
{10211, nullptr, "AddFacedFriendRequestForApplication"},
|
||||
@@ -194,6 +194,17 @@ private:
|
||||
// TODO(ogniK): Return a buffer of u64s which are the "NetworkServiceAccountId"
|
||||
}
|
||||
|
||||
void CheckFriendListAvailability(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto uuid{rp.PopRaw<Common::UUID>()};
|
||||
|
||||
LOG_WARNING(Service_Friend, "(STUBBED) called, uuid=0x{}", uuid.RawString());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(true);
|
||||
}
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
Kernel::KEvent* completion_event;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/k_writable_event.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/errors.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
@@ -167,7 +166,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
const auto& battery_level = controller.device->GetBattery();
|
||||
auto* shared_memory = controller.shared_memory;
|
||||
if (controller_type == Core::HID::NpadStyleIndex::None) {
|
||||
controller.styleset_changed_event->GetWritableEvent().Signal();
|
||||
controller.styleset_changed_event->Signal();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1033,7 +1032,7 @@ Kernel::KReadableEvent& Controller_NPad::GetStyleSetChangedEvent(Core::HID::Npad
|
||||
|
||||
void Controller_NPad::SignalStyleSetChangedEvent(Core::HID::NpadIdType npad_id) const {
|
||||
const auto& controller = GetControllerFromNpadIdType(npad_id);
|
||||
controller.styleset_changed_event->GetWritableEvent().Signal();
|
||||
controller.styleset_changed_event->Signal();
|
||||
}
|
||||
|
||||
void Controller_NPad::AddNewControllerAt(Core::HID::NpadStyleIndex controller,
|
||||
|
||||
@@ -73,7 +73,7 @@ Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
|
||||
operation.operation = PalmaOperationType::PlayActivity;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
|
||||
operation.operation = PalmaOperationType::ReadStep;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle
|
||||
operation.operation = PalmaOperationType::ReadUniqueCode;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle&
|
||||
operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandl
|
||||
operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle
|
||||
operation.operation = PalmaOperationType::WriteWaveEntry;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnec
|
||||
operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data[0] = {};
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
|
||||
operation.result = PalmaResultSuccess;
|
||||
operation.data = {};
|
||||
operation.data[0] = static_cast<u8>(database_id_version);
|
||||
operation_complete_event->GetWritableEvent().Signal();
|
||||
operation_complete_event->Signal();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -2118,7 +2118,7 @@ void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
|
||||
ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
|
||||
|
||||
LOG_WARNING(Service_HID,
|
||||
"(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
|
||||
"(STUBBED) called, connection_handle={}, wave_set={}, unknown={}, "
|
||||
"t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
|
||||
connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
|
||||
|
||||
|
||||
@@ -131,12 +131,12 @@ bool RingController::SetCommand(const std::vector<u8>& data) {
|
||||
case RingConCommands::ReadRepCount:
|
||||
case RingConCommands::ReadTotalPushCount:
|
||||
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
|
||||
send_command_async_event->GetWritableEvent().Signal();
|
||||
send_command_async_event->Signal();
|
||||
return true;
|
||||
case RingConCommands::ResetRepCount:
|
||||
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
|
||||
total_rep_count = 0;
|
||||
send_command_async_event->GetWritableEvent().Signal();
|
||||
send_command_async_event->Signal();
|
||||
return true;
|
||||
case RingConCommands::SaveCalData: {
|
||||
ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
|
||||
@@ -144,14 +144,14 @@ bool RingController::SetCommand(const std::vector<u8>& data) {
|
||||
SaveCalData save_info{};
|
||||
std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
|
||||
user_calibration = save_info.calibration;
|
||||
send_command_async_event->GetWritableEvent().Signal();
|
||||
send_command_async_event->Signal();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Service_HID, "Command not implemented {}", command);
|
||||
command = RingConCommands::Error;
|
||||
// Signal a reply to avoid softlocking the game
|
||||
send_command_async_event->GetWritableEvent().Signal();
|
||||
send_command_async_event->Signal();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ private:
|
||||
u8 pointing_status;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
u32 unknown;
|
||||
float unkown_float1;
|
||||
float unknown_float1;
|
||||
float position_x;
|
||||
float position_y;
|
||||
float unkown_float2;
|
||||
float unknown_float2;
|
||||
Core::IrSensor::IrsRect window_of_interest;
|
||||
};
|
||||
static_assert(sizeof(PointingProcessorMarkerData) == 0x20,
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/k_resource_limit.h"
|
||||
#include "core/hle/kernel/k_scoped_resource_reservation.h"
|
||||
#include "core/hle/kernel/k_writable_event.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
|
||||
namespace Service::KernelHelpers {
|
||||
@@ -46,7 +45,7 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {
|
||||
}
|
||||
|
||||
// Initialize the event.
|
||||
event->Initialize(std::move(name), process);
|
||||
event->Initialize(process);
|
||||
|
||||
// Commit the thread reservation.
|
||||
event_reservation.Commit();
|
||||
@@ -59,7 +58,7 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {
|
||||
|
||||
void ServiceContext::CloseEvent(Kernel::KEvent* event) {
|
||||
event->GetReadableEvent().Close();
|
||||
event->GetWritableEvent().Close();
|
||||
event->Close();
|
||||
}
|
||||
|
||||
} // namespace Service::KernelHelpers
|
||||
|
||||
@@ -165,7 +165,7 @@ public:
|
||||
}
|
||||
|
||||
void OnEventFired() {
|
||||
state_change_event->GetWritableEvent().Signal();
|
||||
state_change_event->Signal();
|
||||
}
|
||||
|
||||
void GetState(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -28,7 +28,7 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
|
||||
LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
|
||||
static_cast<u16>(amiibo_data.model_info.model_number));
|
||||
LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
|
||||
LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
|
||||
LOG_DEBUG(Service_NFP, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type);
|
||||
|
||||
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
|
||||
LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
|
||||
@@ -55,7 +55,7 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
|
||||
if (amiibo_data.constant_value != 0xA5) {
|
||||
return false;
|
||||
}
|
||||
if (amiibo_data.model_info.constant_value != 0x02) {
|
||||
if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
|
||||
return false;
|
||||
}
|
||||
if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
|
||||
|
||||
@@ -58,7 +58,7 @@ NfpDevice::~NfpDevice() {
|
||||
void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
|
||||
if (type == Core::HID::ControllerTriggerType::Connected ||
|
||||
type == Core::HID::ControllerTriggerType::Disconnected) {
|
||||
availability_change_event->GetWritableEvent().Signal();
|
||||
availability_change_event->Signal();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,14 +98,9 @@ bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
|
||||
|
||||
memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
|
||||
|
||||
if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
|
||||
LOG_INFO(Service_NFP, "Invalid amiibo");
|
||||
return false;
|
||||
}
|
||||
|
||||
device_state = DeviceState::TagFound;
|
||||
deactivate_event->GetReadableEvent().Clear();
|
||||
activate_event->GetWritableEvent().Signal();
|
||||
activate_event->Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -120,7 +115,7 @@ void NfpDevice::CloseAmiibo() {
|
||||
encrypted_tag_data = {};
|
||||
tag_data = {};
|
||||
activate_event->GetReadableEvent().Clear();
|
||||
deactivate_event->GetWritableEvent().Signal();
|
||||
deactivate_event->Signal();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
|
||||
@@ -148,20 +143,28 @@ void NfpDevice::Finalize() {
|
||||
}
|
||||
|
||||
Result NfpDevice::StartDetection(s32 protocol_) {
|
||||
if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
|
||||
npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
|
||||
device_state = DeviceState::SearchingForTag;
|
||||
protocol = protocol_;
|
||||
return ResultSuccess;
|
||||
if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
|
||||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
|
||||
return WrongDeviceState;
|
||||
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
|
||||
LOG_ERROR(Service_NFP, "Nfc not supported");
|
||||
return NfcDisabled;
|
||||
}
|
||||
|
||||
device_state = DeviceState::SearchingForTag;
|
||||
protocol = protocol_;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfpDevice::StopDetection() {
|
||||
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
|
||||
|
||||
if (device_state == DeviceState::Initialized) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
|
||||
CloseAmiibo();
|
||||
return ResultSuccess;
|
||||
@@ -225,6 +228,11 @@ Result NfpDevice::Mount(MountTarget mount_target_) {
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
|
||||
LOG_ERROR(Service_NFP, "Not an amiibo");
|
||||
return NotAnAmiibo;
|
||||
}
|
||||
|
||||
if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
|
||||
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
|
||||
return CorruptedData;
|
||||
@@ -238,6 +246,9 @@ Result NfpDevice::Mount(MountTarget mount_target_) {
|
||||
Result NfpDevice::Unmount() {
|
||||
if (device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
@@ -256,6 +267,9 @@ Result NfpDevice::Unmount() {
|
||||
Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
|
||||
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
@@ -287,12 +301,7 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
|
||||
|
||||
// TODO: Validate this data
|
||||
common_info = {
|
||||
.last_write_date =
|
||||
{
|
||||
settings.write_date.GetYear(),
|
||||
settings.write_date.GetMonth(),
|
||||
settings.write_date.GetDay(),
|
||||
},
|
||||
.last_write_date = settings.write_date.GetWriteDate(),
|
||||
.write_counter = tag_data.write_counter,
|
||||
.version = 0,
|
||||
.application_area_size = sizeof(ApplicationArea),
|
||||
@@ -303,6 +312,9 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
|
||||
Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
|
||||
if (device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
@@ -341,12 +353,7 @@ Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
|
||||
// TODO: Validate this data
|
||||
register_info = {
|
||||
.mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
|
||||
.creation_date =
|
||||
{
|
||||
settings.init_date.GetYear(),
|
||||
settings.init_date.GetMonth(),
|
||||
settings.init_date.GetDay(),
|
||||
},
|
||||
.creation_date = settings.init_date.GetWriteDate(),
|
||||
.amiibo_name = GetAmiiboName(settings),
|
||||
.font_region = {},
|
||||
};
|
||||
@@ -478,8 +485,7 @@ Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
|
||||
}
|
||||
|
||||
if (data.size() > sizeof(ApplicationArea)) {
|
||||
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
|
||||
return ResultUnknown;
|
||||
data.resize(sizeof(ApplicationArea));
|
||||
}
|
||||
|
||||
memcpy(data.data(), tag_data.application_area.data(), data.size());
|
||||
@@ -518,7 +524,7 @@ Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
|
||||
|
||||
Common::TinyMT rng{};
|
||||
std::memcpy(tag_data.application_area.data(), data.data(), data.size());
|
||||
// HW seems to fill excess data with garbage
|
||||
// Fill remaining data with random numbers
|
||||
rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
|
||||
sizeof(ApplicationArea) - data.size());
|
||||
|
||||
@@ -561,12 +567,12 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> dat
|
||||
|
||||
if (data.size() > sizeof(ApplicationArea)) {
|
||||
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
|
||||
return ResultUnknown;
|
||||
return WrongApplicationAreaSize;
|
||||
}
|
||||
|
||||
Common::TinyMT rng{};
|
||||
std::memcpy(tag_data.application_area.data(), data.data(), data.size());
|
||||
// HW seems to fill excess data with garbage
|
||||
// Fill remaining data with random numbers
|
||||
rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
|
||||
sizeof(ApplicationArea) - data.size());
|
||||
|
||||
@@ -612,7 +618,6 @@ u64 NfpDevice::GetHandle() const {
|
||||
}
|
||||
|
||||
u32 NfpDevice::GetApplicationAreaSize() const {
|
||||
// Investigate if this value is really constant
|
||||
return sizeof(ApplicationArea);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
namespace Service::NFP {
|
||||
|
||||
constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
|
||||
constexpr Result InvalidArgument(ErrorModule::NFP, 65);
|
||||
constexpr Result WrongApplicationAreaSize(ErrorModule::NFP, 68);
|
||||
constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
|
||||
constexpr Result NfcDisabled(ErrorModule::NFP, 80);
|
||||
constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
|
||||
|
||||
@@ -84,6 +84,15 @@ enum class TagType : u32 {
|
||||
Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
|
||||
};
|
||||
|
||||
enum class PackedTagType : u8 {
|
||||
None,
|
||||
Type1, // ISO14443A RW 96-2k bytes 106kbit/s
|
||||
Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
|
||||
Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
|
||||
Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
|
||||
Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
|
||||
};
|
||||
|
||||
enum class TagProtocol : u32 {
|
||||
None,
|
||||
TypeA, // ISO14443A
|
||||
@@ -104,6 +113,13 @@ struct TagUuid {
|
||||
};
|
||||
static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
|
||||
|
||||
struct WriteDate {
|
||||
u16 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
};
|
||||
static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
|
||||
|
||||
struct AmiiboDate {
|
||||
u16 raw_date{};
|
||||
|
||||
@@ -121,6 +137,21 @@ struct AmiiboDate {
|
||||
return static_cast<u8>(GetValue() & 0x001F);
|
||||
}
|
||||
|
||||
WriteDate GetWriteDate() const {
|
||||
if (!IsValidDate()) {
|
||||
return {
|
||||
.year = 2000,
|
||||
.month = 1,
|
||||
.day = 1,
|
||||
};
|
||||
}
|
||||
return {
|
||||
.year = GetYear(),
|
||||
.month = GetMonth(),
|
||||
.day = GetDay(),
|
||||
};
|
||||
}
|
||||
|
||||
void SetYear(u16 year) {
|
||||
const u16 year_converted = static_cast<u16>((year - 2000) << 9);
|
||||
raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
|
||||
@@ -133,6 +164,13 @@ struct AmiiboDate {
|
||||
const u16 day_converted = static_cast<u16>(day);
|
||||
raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
|
||||
}
|
||||
|
||||
bool IsValidDate() const {
|
||||
const bool is_day_valid = GetDay() > 0 && GetDay() < 32;
|
||||
const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13;
|
||||
const bool is_year_valid = GetYear() >= 2000;
|
||||
return is_year_valid && is_month_valid && is_day_valid;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
|
||||
|
||||
@@ -163,7 +201,7 @@ struct AmiiboModelInfo {
|
||||
AmiiboType amiibo_type;
|
||||
u16_be model_number;
|
||||
AmiiboSeries series;
|
||||
u8 constant_value; // Must be 02
|
||||
PackedTagType tag_type;
|
||||
INSERT_PADDING_BYTES(0x4); // Unknown
|
||||
};
|
||||
static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
|
||||
@@ -250,13 +288,6 @@ struct TagInfo {
|
||||
};
|
||||
static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
|
||||
|
||||
struct WriteDate {
|
||||
u16 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
};
|
||||
static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
|
||||
|
||||
struct CommonInfo {
|
||||
WriteDate last_write_date;
|
||||
u16 write_counter;
|
||||
|
||||
@@ -93,6 +93,18 @@ void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.CanWriteBuffer()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.GetWriteBufferSize() == 0) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u64> nfp_devices;
|
||||
const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
|
||||
|
||||
@@ -255,6 +267,12 @@ void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.CanWriteBuffer()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfpDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
@@ -283,6 +301,12 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.CanReadBuffer()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfpDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
@@ -358,6 +382,12 @@ void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.CanReadBuffer()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfpDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
|
||||
@@ -328,7 +328,7 @@ private:
|
||||
void StartTask(Kernel::HLERequestContext& ctx) {
|
||||
// No need to connect to the internet, just finish the task straight away.
|
||||
LOG_DEBUG(Service_NIM, "called");
|
||||
finished_event->GetWritableEvent().Signal();
|
||||
finished_event->Signal();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
@@ -350,7 +350,7 @@ private:
|
||||
|
||||
void Cancel(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NIM, "called");
|
||||
finished_event->GetWritableEvent().Clear();
|
||||
finished_event->Clear();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/glue/glue_manager.h"
|
||||
#include "core/hle/service/ns/errors.h"
|
||||
#include "core/hle/service/ns/iplatform_service_manager.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
@@ -581,7 +582,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
|
||||
: ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetApplicationControlData"},
|
||||
{0, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData, "GetApplicationControlData"},
|
||||
{1, nullptr, "GetApplicationDesiredLanguage"},
|
||||
{2, nullptr, "ConvertApplicationLanguageToLanguageCode"},
|
||||
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
|
||||
@@ -594,6 +595,33 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
|
||||
|
||||
IReadOnlyApplicationControlDataInterface::~IReadOnlyApplicationControlDataInterface() = default;
|
||||
|
||||
void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
|
||||
Kernel::HLERequestContext& ctx) {
|
||||
enum class ApplicationControlSource : u8 {
|
||||
CacheOnly,
|
||||
Storage,
|
||||
StorageOnly,
|
||||
};
|
||||
|
||||
struct RequestParameters {
|
||||
ApplicationControlSource source;
|
||||
u64 application_id;
|
||||
};
|
||||
static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size.");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters{rp.PopRaw<RequestParameters>()};
|
||||
const auto nacp_data{system.GetARPManager().GetControlProperty(parameters.application_id)};
|
||||
const auto result = nacp_data ? ResultSuccess : ResultUnknown;
|
||||
|
||||
if (nacp_data) {
|
||||
ctx.WriteBuffer(nacp_data->data(), nacp_data->size());
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
NS::NS(const char* name, Core::System& system_) : ServiceFramework{system_, name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
|
||||
@@ -78,6 +78,9 @@ class IReadOnlyApplicationControlDataInterface final
|
||||
public:
|
||||
explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
|
||||
~IReadOnlyApplicationControlDataInterface() override;
|
||||
|
||||
private:
|
||||
void GetApplicationControlData(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class NS final : public ServiceFramework<NS> {
|
||||
|
||||
50
src/core/hle/service/nvdrv/core/container.cpp
Normal file
50
src/core/hle/service/nvdrv/core/container.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
|
||||
struct ContainerImpl {
|
||||
explicit ContainerImpl(Tegra::Host1x::Host1x& host1x_)
|
||||
: file{host1x_}, manager{host1x_}, device_file_data{} {}
|
||||
NvMap file;
|
||||
SyncpointManager manager;
|
||||
Container::Host1xDeviceFileData device_file_data;
|
||||
};
|
||||
|
||||
Container::Container(Tegra::Host1x::Host1x& host1x_) {
|
||||
impl = std::make_unique<ContainerImpl>(host1x_);
|
||||
}
|
||||
|
||||
Container::~Container() = default;
|
||||
|
||||
NvMap& Container::GetNvMapFile() {
|
||||
return impl->file;
|
||||
}
|
||||
|
||||
const NvMap& Container::GetNvMapFile() const {
|
||||
return impl->file;
|
||||
}
|
||||
|
||||
Container::Host1xDeviceFileData& Container::Host1xDeviceFile() {
|
||||
return impl->device_file_data;
|
||||
}
|
||||
|
||||
const Container::Host1xDeviceFileData& Container::Host1xDeviceFile() const {
|
||||
return impl->device_file_data;
|
||||
}
|
||||
|
||||
SyncpointManager& Container::GetSyncpointManager() {
|
||||
return impl->manager;
|
||||
}
|
||||
|
||||
const SyncpointManager& Container::GetSyncpointManager() const {
|
||||
return impl->manager;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
52
src/core/hle/service/nvdrv/core/container.h
Normal file
52
src/core/hle/service/nvdrv/core/container.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
|
||||
namespace Tegra::Host1x {
|
||||
class Host1x;
|
||||
} // namespace Tegra::Host1x
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
|
||||
class NvMap;
|
||||
class SyncpointManager;
|
||||
|
||||
struct ContainerImpl;
|
||||
|
||||
class Container {
|
||||
public:
|
||||
explicit Container(Tegra::Host1x::Host1x& host1x);
|
||||
~Container();
|
||||
|
||||
NvMap& GetNvMapFile();
|
||||
|
||||
const NvMap& GetNvMapFile() const;
|
||||
|
||||
SyncpointManager& GetSyncpointManager();
|
||||
|
||||
const SyncpointManager& GetSyncpointManager() const;
|
||||
|
||||
struct Host1xDeviceFileData {
|
||||
std::unordered_map<DeviceFD, u32> fd_to_id{};
|
||||
std::deque<u32> syncpts_accumulated{};
|
||||
u32 nvdec_next_id{};
|
||||
u32 vic_next_id{};
|
||||
};
|
||||
|
||||
Host1xDeviceFileData& Host1xDeviceFile();
|
||||
|
||||
const Host1xDeviceFileData& Host1xDeviceFile() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ContainerImpl> impl;
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
272
src/core/hle/service/nvdrv/core/nvmap.cpp
Normal file
272
src/core/hle/service/nvdrv/core/nvmap.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
|
||||
using Core::Memory::YUZU_PAGESIZE;
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
NvMap::Handle::Handle(u64 size_, Id id_)
|
||||
: size(size_), aligned_size(size), orig_size(size), id(id_) {
|
||||
flags.raw = 0;
|
||||
}
|
||||
|
||||
NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) {
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
// Handles cannot be allocated twice
|
||||
if (allocated) {
|
||||
return NvResult::AccessDenied;
|
||||
}
|
||||
|
||||
flags = pFlags;
|
||||
kind = pKind;
|
||||
align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
|
||||
|
||||
// This flag is only applicable for handles with an address passed
|
||||
if (pAddress) {
|
||||
flags.keep_uncached_after_free.Assign(0);
|
||||
} else {
|
||||
LOG_CRITICAL(Service_NVDRV,
|
||||
"Mapping nvmap handles without a CPU side address is unimplemented!");
|
||||
}
|
||||
|
||||
size = Common::AlignUp(size, YUZU_PAGESIZE);
|
||||
aligned_size = Common::AlignUp(size, align);
|
||||
address = pAddress;
|
||||
allocated = true;
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvResult NvMap::Handle::Duplicate(bool internal_session) {
|
||||
std::scoped_lock lock(mutex);
|
||||
// Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
|
||||
if (!allocated) [[unlikely]] {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
// If we internally use FromId the duplication tracking of handles won't work accurately due to
|
||||
// us not implementing per-process handle refs.
|
||||
if (internal_session) {
|
||||
internal_dupes++;
|
||||
} else {
|
||||
dupes++;
|
||||
}
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvMap::NvMap(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {}
|
||||
|
||||
void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
|
||||
std::scoped_lock lock(handles_lock);
|
||||
|
||||
handles.emplace(handle_description->id, std::move(handle_description));
|
||||
}
|
||||
|
||||
void NvMap::UnmapHandle(Handle& handle_description) {
|
||||
// Remove pending unmap queue entry if needed
|
||||
if (handle_description.unmap_queue_entry) {
|
||||
unmap_queue.erase(*handle_description.unmap_queue_entry);
|
||||
handle_description.unmap_queue_entry.reset();
|
||||
}
|
||||
|
||||
// Free and unmap the handle from the SMMU
|
||||
host1x.MemoryManager().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
|
||||
handle_description.aligned_size);
|
||||
host1x.Allocator().Free(handle_description.pin_virt_address,
|
||||
static_cast<u32>(handle_description.aligned_size));
|
||||
handle_description.pin_virt_address = 0;
|
||||
}
|
||||
|
||||
bool NvMap::TryRemoveHandle(const Handle& handle_description) {
|
||||
// No dupes left, we can remove from handle map
|
||||
if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
|
||||
std::scoped_lock lock(handles_lock);
|
||||
|
||||
auto it{handles.find(handle_description.id)};
|
||||
if (it != handles.end()) {
|
||||
handles.erase(it);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
|
||||
if (!size) [[unlikely]] {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
|
||||
auto handle_description{std::make_shared<Handle>(size, id)};
|
||||
AddHandle(handle_description);
|
||||
|
||||
result_out = handle_description;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
|
||||
std::scoped_lock lock(handles_lock);
|
||||
try {
|
||||
return handles.at(handle);
|
||||
} catch (std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VAddr NvMap::GetHandleAddress(Handle::Id handle) {
|
||||
std::scoped_lock lock(handles_lock);
|
||||
try {
|
||||
return handles.at(handle)->address;
|
||||
} catch (std::out_of_range&) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
|
||||
auto handle_description{GetHandle(handle)};
|
||||
if (!handle_description) [[unlikely]] {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(handle_description->mutex);
|
||||
if (!handle_description->pins) {
|
||||
// If we're in the unmap queue we can just remove ourselves and return since we're already
|
||||
// mapped
|
||||
{
|
||||
// Lock now to prevent our queue entry from being removed for allocation in-between the
|
||||
// following check and erase
|
||||
std::scoped_lock queueLock(unmap_queue_lock);
|
||||
if (handle_description->unmap_queue_entry) {
|
||||
unmap_queue.erase(*handle_description->unmap_queue_entry);
|
||||
handle_description->unmap_queue_entry.reset();
|
||||
|
||||
handle_description->pins++;
|
||||
return handle_description->pin_virt_address;
|
||||
}
|
||||
}
|
||||
|
||||
// If not then allocate some space and map it
|
||||
u32 address{};
|
||||
auto& smmu_allocator = host1x.Allocator();
|
||||
auto& smmu_memory_manager = host1x.MemoryManager();
|
||||
while (!(address =
|
||||
smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
|
||||
// Free handles until the allocation succeeds
|
||||
std::scoped_lock queueLock(unmap_queue_lock);
|
||||
if (auto freeHandleDesc{unmap_queue.front()}) {
|
||||
// Handles in the unmap queue are guaranteed not to be pinned so don't bother
|
||||
// checking if they are before unmapping
|
||||
std::scoped_lock freeLock(freeHandleDesc->mutex);
|
||||
if (handle_description->pin_virt_address)
|
||||
UnmapHandle(*freeHandleDesc);
|
||||
} else {
|
||||
LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
|
||||
}
|
||||
}
|
||||
|
||||
smmu_memory_manager.Map(static_cast<GPUVAddr>(address), handle_description->address,
|
||||
handle_description->aligned_size);
|
||||
handle_description->pin_virt_address = address;
|
||||
}
|
||||
|
||||
handle_description->pins++;
|
||||
return handle_description->pin_virt_address;
|
||||
}
|
||||
|
||||
void NvMap::UnpinHandle(Handle::Id handle) {
|
||||
auto handle_description{GetHandle(handle)};
|
||||
if (!handle_description) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(handle_description->mutex);
|
||||
if (--handle_description->pins < 0) {
|
||||
LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
|
||||
} else if (!handle_description->pins) {
|
||||
std::scoped_lock queueLock(unmap_queue_lock);
|
||||
|
||||
// Add to the unmap queue allowing this handle's memory to be freed if needed
|
||||
unmap_queue.push_back(handle_description);
|
||||
handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
|
||||
}
|
||||
}
|
||||
|
||||
void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
|
||||
auto handle_description{GetHandle(handle)};
|
||||
if (!handle_description) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = handle_description->Duplicate(internal_session);
|
||||
if (result != NvResult::Success) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
|
||||
std::weak_ptr<Handle> hWeak{GetHandle(handle)};
|
||||
FreeInfo freeInfo;
|
||||
|
||||
// We use a weak ptr here so we can tell when the handle has been freed and report that back to
|
||||
// guest
|
||||
if (auto handle_description = hWeak.lock()) {
|
||||
std::scoped_lock lock(handle_description->mutex);
|
||||
|
||||
if (internal_session) {
|
||||
if (--handle_description->internal_dupes < 0)
|
||||
LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
|
||||
} else {
|
||||
if (--handle_description->dupes < 0) {
|
||||
LOG_WARNING(Service_NVDRV, "User duplicate count imbalance detected!");
|
||||
} else if (handle_description->dupes == 0) {
|
||||
// Force unmap the handle
|
||||
if (handle_description->pin_virt_address) {
|
||||
std::scoped_lock queueLock(unmap_queue_lock);
|
||||
UnmapHandle(*handle_description);
|
||||
}
|
||||
|
||||
handle_description->pins = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to remove the shared ptr to the handle from the map, if nothing else is using the
|
||||
// handle then it will now be freed when `handle_description` goes out of scope
|
||||
if (TryRemoveHandle(*handle_description)) {
|
||||
LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
|
||||
} else {
|
||||
LOG_DEBUG(Service_NVDRV,
|
||||
"Tried to free nvmap handle: {} but didn't as it still has duplicates",
|
||||
handle);
|
||||
}
|
||||
|
||||
freeInfo = {
|
||||
.address = handle_description->address,
|
||||
.size = handle_description->size,
|
||||
.was_uncached = handle_description->flags.map_uncached.Value() != 0,
|
||||
};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
|
||||
if (!hWeak.expired()) {
|
||||
LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
|
||||
freeInfo.address = 0;
|
||||
}
|
||||
|
||||
return freeInfo;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
175
src/core/hle/service/nvdrv/core/nvmap.h
Normal file
175
src/core/hle/service/nvdrv/core/nvmap.h
Normal file
@@ -0,0 +1,175 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
namespace Host1x {
|
||||
class Host1x;
|
||||
} // namespace Host1x
|
||||
|
||||
} // namespace Tegra
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
/**
|
||||
* @brief The nvmap core class holds the global state for nvmap and provides methods to manage
|
||||
* handles
|
||||
*/
|
||||
class NvMap {
|
||||
public:
|
||||
/**
|
||||
* @brief A handle to a contiguous block of memory in an application's address space
|
||||
*/
|
||||
struct Handle {
|
||||
std::mutex mutex;
|
||||
|
||||
u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
|
||||
u64 size; //!< Page-aligned size of the memory the handle refers to
|
||||
u64 aligned_size; //!< `align`-aligned size of the memory the handle refers to
|
||||
u64 orig_size; //!< Original unaligned size of the memory this handle refers to
|
||||
|
||||
s32 dupes{1}; //!< How many guest references there are to this handle
|
||||
s32 internal_dupes{0}; //!< How many emulator-internal references there are to this handle
|
||||
|
||||
using Id = u32;
|
||||
Id id; //!< A globally unique identifier for this handle
|
||||
|
||||
s32 pins{};
|
||||
u32 pin_virt_address{};
|
||||
std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
|
||||
|
||||
union Flags {
|
||||
u32 raw;
|
||||
BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
|
||||
BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
|
||||
//!< allocated with a fixed address
|
||||
BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
|
||||
} flags{};
|
||||
static_assert(sizeof(Flags) == sizeof(u32));
|
||||
|
||||
u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to,
|
||||
//!< this can also be in the nvdrv tmem
|
||||
bool is_shared_mem_mapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC
|
||||
//!< call
|
||||
|
||||
u8 kind{}; //!< Used for memory compression
|
||||
bool allocated{}; //!< If the handle has been allocated with `Alloc`
|
||||
|
||||
u64 dma_map_addr{}; //! remove me after implementing pinning.
|
||||
|
||||
Handle(u64 size, Id id);
|
||||
|
||||
/**
|
||||
* @brief Sets up the handle with the given memory config, can allocate memory from the tmem
|
||||
* if a 0 address is passed
|
||||
*/
|
||||
[[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress);
|
||||
|
||||
/**
|
||||
* @brief Increases the dupe counter of the handle for the given session
|
||||
*/
|
||||
[[nodiscard]] NvResult Duplicate(bool internal_session);
|
||||
|
||||
/**
|
||||
* @brief Obtains a pointer to the handle's memory and marks the handle it as having been
|
||||
* mapped
|
||||
*/
|
||||
u8* GetPointer() {
|
||||
if (!address) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
is_shared_mem_mapped = true;
|
||||
return reinterpret_cast<u8*>(address);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Encapsulates the result of a FreeHandle operation
|
||||
*/
|
||||
struct FreeInfo {
|
||||
u64 address; //!< Address the handle referred to before deletion
|
||||
u64 size; //!< Page-aligned handle size
|
||||
bool was_uncached; //!< If the handle was allocated as uncached
|
||||
};
|
||||
|
||||
explicit NvMap(Tegra::Host1x::Host1x& host1x);
|
||||
|
||||
/**
|
||||
* @brief Creates an unallocated handle of the given size
|
||||
*/
|
||||
[[nodiscard]] NvResult CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out);
|
||||
|
||||
std::shared_ptr<Handle> GetHandle(Handle::Id handle);
|
||||
|
||||
VAddr GetHandleAddress(Handle::Id handle);
|
||||
|
||||
/**
|
||||
* @brief Maps a handle into the SMMU address space
|
||||
* @note This operation is refcounted, the number of calls to this must eventually match the
|
||||
* number of calls to `UnpinHandle`
|
||||
* @return The SMMU virtual address that the handle has been mapped to
|
||||
*/
|
||||
u32 PinHandle(Handle::Id handle);
|
||||
|
||||
/**
|
||||
* @brief When this has been called an equal number of times to `PinHandle` for the supplied
|
||||
* handle it will be added to a list of handles to be freed when necessary
|
||||
*/
|
||||
void UnpinHandle(Handle::Id handle);
|
||||
|
||||
/**
|
||||
* @brief Tries to duplicate a handle
|
||||
*/
|
||||
void DuplicateHandle(Handle::Id handle, bool internal_session = false);
|
||||
|
||||
/**
|
||||
* @brief Tries to free a handle and remove a single dupe
|
||||
* @note If a handle has no dupes left and has no other users a FreeInfo struct will be returned
|
||||
* describing the prior state of the handle
|
||||
*/
|
||||
std::optional<FreeInfo> FreeHandle(Handle::Id handle, bool internal_session);
|
||||
|
||||
private:
|
||||
std::list<std::shared_ptr<Handle>> unmap_queue{};
|
||||
std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue`
|
||||
|
||||
std::unordered_map<Handle::Id, std::shared_ptr<Handle>>
|
||||
handles{}; //!< Main owning map of handles
|
||||
std::mutex handles_lock; //!< Protects access to `handles`
|
||||
|
||||
static constexpr u32 HandleIdIncrement{
|
||||
4}; //!< Each new handle ID is an increment of 4 from the previous
|
||||
std::atomic<u32> next_handle_id{HandleIdIncrement};
|
||||
Tegra::Host1x::Host1x& host1x;
|
||||
|
||||
void AddHandle(std::shared_ptr<Handle> handle);
|
||||
|
||||
/**
|
||||
* @brief Unmaps and frees the SMMU memory region a handle is mapped to
|
||||
* @note Both `unmap_queue_lock` and `handle_description.mutex` MUST be locked when calling this
|
||||
*/
|
||||
void UnmapHandle(Handle& handle_description);
|
||||
|
||||
/**
|
||||
* @brief Removes a handle from the map taking its dupes into account
|
||||
* @note handle_description.mutex MUST be locked when calling this
|
||||
* @return If the handle was removed from the map
|
||||
*/
|
||||
bool TryRemoveHandle(const Handle& handle_description);
|
||||
};
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
121
src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
Normal file
121
src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
|
||||
SyncpointManager::SyncpointManager(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {
|
||||
constexpr u32 VBlank0SyncpointId{26};
|
||||
constexpr u32 VBlank1SyncpointId{27};
|
||||
|
||||
// Reserve both vblank syncpoints as client managed as they use Continuous Mode
|
||||
// Refer to section 14.3.5.3 of the TRM for more information on Continuous Mode
|
||||
// https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/drm/dc.c#L660
|
||||
ReserveSyncpoint(VBlank0SyncpointId, true);
|
||||
ReserveSyncpoint(VBlank1SyncpointId, true);
|
||||
|
||||
for (u32 syncpoint_id : channel_syncpoints) {
|
||||
if (syncpoint_id) {
|
||||
ReserveSyncpoint(syncpoint_id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncpointManager::~SyncpointManager() = default;
|
||||
|
||||
u32 SyncpointManager::ReserveSyncpoint(u32 id, bool client_managed) {
|
||||
if (syncpoints.at(id).reserved) {
|
||||
ASSERT_MSG(false, "Requested syncpoint is in use");
|
||||
return 0;
|
||||
}
|
||||
|
||||
syncpoints.at(id).reserved = true;
|
||||
syncpoints.at(id).interface_managed = client_managed;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
u32 SyncpointManager::FindFreeSyncpoint() {
|
||||
for (u32 i{1}; i < syncpoints.size(); i++) {
|
||||
if (!syncpoints[i].reserved) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(false, "Failed to find a free syncpoint!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 SyncpointManager::AllocateSyncpoint(bool client_managed) {
|
||||
std::lock_guard lock(reservation_lock);
|
||||
return ReserveSyncpoint(FindFreeSyncpoint(), client_managed);
|
||||
}
|
||||
|
||||
void SyncpointManager::FreeSyncpoint(u32 id) {
|
||||
std::lock_guard lock(reservation_lock);
|
||||
ASSERT(syncpoints.at(id).reserved);
|
||||
syncpoints.at(id).reserved = false;
|
||||
}
|
||||
|
||||
bool SyncpointManager::IsSyncpointAllocated(u32 id) {
|
||||
return (id <= SyncpointCount) && syncpoints[id].reserved;
|
||||
}
|
||||
|
||||
bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) const {
|
||||
const SyncpointInfo& syncpoint{syncpoints.at(id)};
|
||||
|
||||
if (!syncpoint.reserved) {
|
||||
ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the interface manages counters then we don't keep track of the maximum value as it handles
|
||||
// sanity checking the values then
|
||||
if (syncpoint.interface_managed) {
|
||||
return static_cast<s32>(syncpoint.counter_min - threshold) >= 0;
|
||||
} else {
|
||||
return (syncpoint.counter_max - threshold) >= (syncpoint.counter_min - threshold);
|
||||
}
|
||||
}
|
||||
|
||||
u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
|
||||
if (!syncpoints.at(id).reserved) {
|
||||
ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return syncpoints.at(id).counter_max += amount;
|
||||
}
|
||||
|
||||
u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
|
||||
if (!syncpoints.at(id).reserved) {
|
||||
ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return syncpoints.at(id).counter_min;
|
||||
}
|
||||
|
||||
u32 SyncpointManager::UpdateMin(u32 id) {
|
||||
if (!syncpoints.at(id).reserved) {
|
||||
ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
syncpoints.at(id).counter_min = host1x.GetSyncpointManager().GetHostSyncpointValue(id);
|
||||
return syncpoints.at(id).counter_min;
|
||||
}
|
||||
|
||||
NvFence SyncpointManager::GetSyncpointFence(u32 id) {
|
||||
if (!syncpoints.at(id).reserved) {
|
||||
ASSERT(false);
|
||||
return NvFence{};
|
||||
}
|
||||
|
||||
return {.id = static_cast<s32>(id), .value = syncpoints.at(id).counter_max};
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
134
src/core/hle/service/nvdrv/core/syncpoint_manager.h
Normal file
134
src/core/hle/service/nvdrv/core/syncpoint_manager.h
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
|
||||
namespace Tegra::Host1x {
|
||||
class Host1x;
|
||||
} // namespace Tegra::Host1x
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
|
||||
enum class ChannelType : u32 {
|
||||
MsEnc = 0,
|
||||
VIC = 1,
|
||||
GPU = 2,
|
||||
NvDec = 3,
|
||||
Display = 4,
|
||||
NvJpg = 5,
|
||||
TSec = 6,
|
||||
Max = 7
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached
|
||||
* versions of the HW syncpoints which are intermittently synced
|
||||
* @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
|
||||
* @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
|
||||
* @url
|
||||
* https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
|
||||
*/
|
||||
class SyncpointManager final {
|
||||
public:
|
||||
explicit SyncpointManager(Tegra::Host1x::Host1x& host1x);
|
||||
~SyncpointManager();
|
||||
|
||||
/**
|
||||
* @brief Checks if the given syncpoint is both allocated and below the number of HW syncpoints
|
||||
*/
|
||||
bool IsSyncpointAllocated(u32 id);
|
||||
|
||||
/**
|
||||
* @brief Finds a free syncpoint and reserves it
|
||||
* @return The ID of the reserved syncpoint
|
||||
*/
|
||||
u32 AllocateSyncpoint(bool client_managed);
|
||||
|
||||
/**
|
||||
* @url
|
||||
* https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
|
||||
*/
|
||||
bool HasSyncpointExpired(u32 id, u32 threshold) const;
|
||||
|
||||
bool IsFenceSignalled(NvFence fence) const {
|
||||
return HasSyncpointExpired(fence.id, fence.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Atomically increments the maximum value of a syncpoint by the given amount
|
||||
* @return The new max value of the syncpoint
|
||||
*/
|
||||
u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
|
||||
|
||||
/**
|
||||
* @return The minimum value of the syncpoint
|
||||
*/
|
||||
u32 ReadSyncpointMinValue(u32 id);
|
||||
|
||||
/**
|
||||
* @brief Synchronises the minimum value of the syncpoint to with the GPU
|
||||
* @return The new minimum value of the syncpoint
|
||||
*/
|
||||
u32 UpdateMin(u32 id);
|
||||
|
||||
/**
|
||||
* @brief Frees the usage of a syncpoint.
|
||||
*/
|
||||
void FreeSyncpoint(u32 id);
|
||||
|
||||
/**
|
||||
* @return A fence that will be signalled once this syncpoint hits its maximum value
|
||||
*/
|
||||
NvFence GetSyncpointFence(u32 id);
|
||||
|
||||
static constexpr std::array<u32, static_cast<u32>(ChannelType::Max)> channel_syncpoints{
|
||||
0x0, // `MsEnc` is unimplemented
|
||||
0xC, // `VIC`
|
||||
0x0, // `GPU` syncpoints are allocated per-channel instead
|
||||
0x36, // `NvDec`
|
||||
0x0, // `Display` is unimplemented
|
||||
0x37, // `NvJpg`
|
||||
0x0, // `TSec` is unimplemented
|
||||
}; //!< Maps each channel ID to a constant syncpoint
|
||||
|
||||
private:
|
||||
/**
|
||||
* @note reservation_lock should be locked when calling this
|
||||
*/
|
||||
u32 ReserveSyncpoint(u32 id, bool client_managed);
|
||||
|
||||
/**
|
||||
* @return The ID of the first free syncpoint
|
||||
*/
|
||||
u32 FindFreeSyncpoint();
|
||||
|
||||
struct SyncpointInfo {
|
||||
std::atomic<u32> counter_min; //!< The least value the syncpoint can be (The value it was
|
||||
//!< when it was last synchronized with host1x)
|
||||
std::atomic<u32> counter_max; //!< The maximum value the syncpoint can reach according to
|
||||
//!< the current usage
|
||||
bool interface_managed; //!< If the syncpoint is managed by a host1x client interface, a
|
||||
//!< client interface is a HW block that can handle host1x
|
||||
//!< transactions on behalf of a host1x client (Which would
|
||||
//!< otherwise need to be manually synced using PIO which is
|
||||
//!< synchronous and requires direct cooperation of the CPU)
|
||||
bool reserved; //!< If the syncpoint is reserved or not, not to be confused with a reserved
|
||||
//!< value
|
||||
};
|
||||
|
||||
constexpr static std::size_t SyncpointCount{192};
|
||||
std::array<SyncpointInfo, SyncpointCount> syncpoints{};
|
||||
std::mutex reservation_lock;
|
||||
|
||||
Tegra::Host1x::Host1x& host1x;
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
@@ -11,6 +11,10 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
}
|
||||
|
||||
namespace Service::Nvidia::Devices {
|
||||
|
||||
/// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
|
||||
@@ -64,6 +68,10 @@ public:
|
||||
*/
|
||||
virtual void OnClose(DeviceFD fd) = 0;
|
||||
|
||||
virtual Kernel::KEvent* QueryEvent(u32 event_id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Service::Nvidia::Devices {
|
||||
|
||||
nvdisp_disp0::nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
|
||||
: nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
|
||||
nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
|
||||
: nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
|
||||
nvdisp_disp0::~nvdisp_disp0() = default;
|
||||
|
||||
NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||
@@ -39,8 +40,9 @@ void nvdisp_disp0::OnClose(DeviceFD fd) {}
|
||||
|
||||
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
|
||||
u32 height, u32 stride, android::BufferTransformFlags transform,
|
||||
const Common::Rectangle<int>& crop_rect) {
|
||||
const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
|
||||
const Common::Rectangle<int>& crop_rect,
|
||||
std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences) {
|
||||
const VAddr addr = nvmap.GetHandleAddress(buffer_handle);
|
||||
LOG_TRACE(Service,
|
||||
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
|
||||
addr, offset, width, height, stride, format);
|
||||
@@ -48,10 +50,15 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat form
|
||||
const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
|
||||
stride, format, transform, crop_rect};
|
||||
|
||||
system.GPU().RequestSwapBuffers(&framebuffer, fences, num_fences);
|
||||
system.GetPerfStats().EndSystemFrame();
|
||||
system.GPU().SwapBuffers(&framebuffer);
|
||||
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.GetPerfStats().BeginSystemFrame();
|
||||
}
|
||||
|
||||
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user