Compare commits
141 Commits
android-15
...
android-17
Author | SHA1 | Date | |
---|---|---|---|
243040f0ad | |||
e1cc2d360e | |||
5a189eb5d6 | |||
38b5c6e9df | |||
c05c0bd05b | |||
12178c694a | |||
de1e5584b3 | |||
1559984f77 | |||
467ac4fdfe | |||
69b7100dac | |||
14dc41d4b3 | |||
ad049f13aa | |||
4f569fd568 | |||
553dac2ae0 | |||
96abe0d7d3 | |||
47e44a6693 | |||
cf8c7d4ed3 | |||
5165ed9efd | |||
05e3db3ac9 | |||
e3491a9ee8 | |||
6a1ddc5028 | |||
b1d4804c07 | |||
c57ae803a6 | |||
db7b2bc8f1 | |||
31bf57a310 | |||
cae675343c | |||
35501ba41c | |||
419055e484 | |||
91290b9be4 | |||
820f113d9e | |||
373a1ff2ce | |||
4d6b6ba76c | |||
4aa713e861 | |||
9e9aed41be | |||
3d268b8480 | |||
ad7445d4cc | |||
3a30271219 | |||
bb5196aaae | |||
d3070cafa7 | |||
5cd3b6f58c | |||
bedc758fe7 | |||
76701185ad | |||
f1cb14eb54 | |||
f4f4a469a9 | |||
9e5b4052ed | |||
234867b84d | |||
61e8c5f798 | |||
4b60aec190 | |||
bbc0ed118d | |||
ecfba79d98 | |||
310834aea2 | |||
6a1fa9bb17 | |||
db8a601cf8 | |||
1bb76201e6 | |||
372bca5945 | |||
93c19a40bf | |||
d0a75580da | |||
345ec25532 | |||
a94721fde0 | |||
816c7a8d1f | |||
efe52db690 | |||
d61df0f400 | |||
b14547b8b6 | |||
97ad3e7530 | |||
0589a32f75 | |||
617dc0f822 | |||
fcfa8b680b | |||
94244437de | |||
53956a2990 | |||
a7731abb72 | |||
50fd029eaa | |||
a2b567dfd6 | |||
b770f6a985 | |||
797e8fdbc3 | |||
b8c5027686 | |||
65e646eeba | |||
fba3fa705d | |||
09e8fb75ce | |||
6ca530a721 | |||
e01c535178 | |||
7239547ead | |||
7fc06260d1 | |||
e357896674 | |||
225f4f40cb | |||
927be75616 | |||
00965e6c34 | |||
4bf1f217ae | |||
fcc85abe27 | |||
6851e93296 | |||
67660972c9 | |||
ffbba74c91 | |||
2b0cf73bf0 | |||
a093f3d47a | |||
4f600f746a | |||
360418f1a1 | |||
3bc7575c47 | |||
fde8dc1652 | |||
b8f83aa4bf | |||
85b1e17df6 | |||
4144c517a5 | |||
8ad5f2c506 | |||
2a3f84aaf2 | |||
030e6b3980 | |||
e8ad603cd9 | |||
b560ade663 | |||
d10464de30 | |||
64f68e9635 | |||
462ba1b360 | |||
4a86a55174 | |||
86d26914a2 | |||
6ae4177b25 | |||
f6bf8b3ed3 | |||
345fb6b226 | |||
87a9dc9489 | |||
6c6e8b8de0 | |||
5acffe75df | |||
ac222ceba2 | |||
f9d4827102 | |||
7ea7c72dde | |||
809230f634 | |||
698c854d5b | |||
ca5b135ddf | |||
dbddc627d4 | |||
62fc386bb4 | |||
f2eb3c579f | |||
2fce812026 | |||
e975f3cde9 | |||
6b5fb2063f | |||
70c3d36536 | |||
d590cfb9d0 | |||
ded419ef2b | |||
4c3f898789 | |||
46c259bb20 | |||
adc3079613 | |||
15bebf1695 | |||
5c840334b8 | |||
a05c242429 | |||
bd59934350 | |||
abfebe5cc4 | |||
a22a025c5b | |||
d5de9402ee |
3
.github/workflows/verify.yml
vendored
3
.github/workflows/verify.yml
vendored
@ -79,7 +79,8 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd
|
# workaround for https://github.com/actions/setup-python/issues/577
|
||||||
|
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd || brew link --overwrite python@3.12
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
|
@ -142,6 +142,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
|
|||||||
if (ENABLE_WEB_SERVICE)
|
if (ENABLE_WEB_SERVICE)
|
||||||
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
|
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
|
||||||
endif()
|
endif()
|
||||||
|
if (ANDROID)
|
||||||
|
list(APPEND VCPKG_MANIFEST_FEATURES "android")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
|
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
|
||||||
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
|
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
|
||||||
@ -302,7 +305,7 @@ find_package(ZLIB 1.2 REQUIRED)
|
|||||||
find_package(zstd 1.5 REQUIRED)
|
find_package(zstd 1.5 REQUIRED)
|
||||||
|
|
||||||
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||||
find_package(Vulkan 1.3.256 REQUIRED)
|
find_package(Vulkan 1.3.274 REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_LIBUSB)
|
if (ENABLE_LIBUSB)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
| Pull Request | Commit | Title | Author | Merged? |
|
| Pull Request | Commit | Title | Author | Merged? |
|
||||||
|----|----|----|----|----|
|
|----|----|----|----|----|
|
||||||
| [12335](https://github.com/yuzu-emu/yuzu//pull/12335) | [`edcd6d547`](https://github.com/yuzu-emu/yuzu//pull/12335/files) | android: Game Properties | [t895](https://github.com/t895/) | Yes |
|
| [12454](https://github.com/yuzu-emu/yuzu//pull/12454) | [`3a4e7d45f`](https://github.com/yuzu-emu/yuzu//pull/12454/files) | core_timing: minor refactors | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||||
|
| [12466](https://github.com/yuzu-emu/yuzu//pull/12466) | [`adb2af0a2`](https://github.com/yuzu-emu/yuzu//pull/12466/files) | core: track separate heap allocation for linux | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||||
|
| [12479](https://github.com/yuzu-emu/yuzu//pull/12479) | [`20e040723`](https://github.com/yuzu-emu/yuzu//pull/12479/files) | video_core: Fix buffer_row_length for linear compressed textures | [GPUCode](https://github.com/GPUCode/) | Yes |
|
||||||
|
| [12487](https://github.com/yuzu-emu/yuzu//pull/12487) | [`d0c60605a`](https://github.com/yuzu-emu/yuzu//pull/12487/files) | shader_recompiler: use default value for clip distances array | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||||
|
|
||||||
|
|
||||||
End of merge log. You can find the original README.md below the break.
|
End of merge log. You can find the original README.md below the break.
|
||||||
|
2
externals/Vulkan-Headers
vendored
2
externals/Vulkan-Headers
vendored
Submodule externals/Vulkan-Headers updated: df60f03168...80207f9da8
2
externals/vcpkg
vendored
2
externals/vcpkg
vendored
Submodule externals/vcpkg updated: ef2eef1734...a42af01b72
@ -174,7 +174,8 @@ android {
|
|||||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||||
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
"-DYUZU_USE_BUNDLED_VCPKG=ON",
|
||||||
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
|
||||||
"-DYUZU_ENABLE_LTO=ON"
|
"-DYUZU_ENABLE_LTO=ON",
|
||||||
|
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
|
||||||
)
|
)
|
||||||
|
|
||||||
abiFilters("arm64-v8a", "x86_64")
|
abiFilters("arm64-v8a", "x86_64")
|
||||||
|
@ -14,7 +14,7 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
||||||
import org.yuzu.yuzu_emu.model.GameProperty
|
import org.yuzu.yuzu_emu.model.GameProperty
|
||||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||||
@ -42,7 +42,7 @@ class GamePropertiesAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> InstallablePropertyViewHolder(
|
else -> InstallablePropertyViewHolder(
|
||||||
CardInstallableBinding.inflate(
|
CardInstallableIconBinding.inflate(
|
||||||
inflater,
|
inflater,
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
@ -107,13 +107,20 @@ class GamePropertiesAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class InstallablePropertyViewHolder(val binding: CardInstallableBinding) :
|
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
|
||||||
GamePropertyViewHolder(binding.root) {
|
GamePropertyViewHolder(binding.root) {
|
||||||
override fun bind(property: GameProperty) {
|
override fun bind(property: GameProperty) {
|
||||||
val installableProperty = property as InstallableProperty
|
val installableProperty = property as InstallableProperty
|
||||||
|
|
||||||
binding.title.setText(installableProperty.titleId)
|
binding.title.setText(installableProperty.titleId)
|
||||||
binding.description.setText(installableProperty.descriptionId)
|
binding.description.setText(installableProperty.descriptionId)
|
||||||
|
binding.icon.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
binding.icon.context.resources,
|
||||||
|
installableProperty.iconId,
|
||||||
|
binding.icon.context.theme
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (installableProperty.install != null) {
|
if (installableProperty.install != null) {
|
||||||
binding.buttonInstall.visibility = View.VISIBLE
|
binding.buttonInstall.visibility = View.VISIBLE
|
||||||
|
@ -22,6 +22,9 @@ interface AbstractSetting {
|
|||||||
get() = NativeConfig.usingGlobal(key)
|
get() = NativeConfig.usingGlobal(key)
|
||||||
set(value) = NativeConfig.setGlobal(key, value)
|
set(value) = NativeConfig.setGlobal(key, value)
|
||||||
|
|
||||||
|
val isSaveable: Boolean
|
||||||
|
get() = NativeConfig.getIsSaveable(key)
|
||||||
|
|
||||||
fun getValueAsString(needsGlobal: Boolean = false): String
|
fun getValueAsString(needsGlobal: Boolean = false): String
|
||||||
|
|
||||||
fun reset()
|
fun reset()
|
||||||
|
@ -32,8 +32,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
|
|
||||||
override val defaultValue: Boolean by lazy { NativeConfig.getDefaultToString(key).toBoolean() }
|
override val defaultValue: Boolean by lazy { NativeConfig.getDefaultToString(key).toBoolean() }
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean(needsGlobal).toString()
|
||||||
if (getBoolean(needsGlobal)) "1" else "0"
|
|
||||||
|
|
||||||
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
|
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ abstract class SettingsItem(
|
|||||||
|
|
||||||
val isEditable: Boolean
|
val isEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
|
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||||
|
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (!NativeLibrary.isRunning()) return true
|
if (!NativeLibrary.isRunning()) return true
|
||||||
|
|
||||||
// Prevent editing settings that were modified in per-game config while editing global
|
// Prevent editing settings that were modified in per-game config while editing global
|
||||||
@ -37,6 +42,7 @@ abstract class SettingsItem(
|
|||||||
if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
|
if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return setting.isRuntimeModifiable
|
return setting.isRuntimeModifiable
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +65,7 @@ abstract class SettingsItem(
|
|||||||
val emptySetting = object : AbstractSetting {
|
val emptySetting = object : AbstractSetting {
|
||||||
override val key: String = ""
|
override val key: String = ""
|
||||||
override val defaultValue: Any = false
|
override val defaultValue: Any = false
|
||||||
|
override val isSaveable = true
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
||||||
override fun reset() {}
|
override fun reset() {}
|
||||||
}
|
}
|
||||||
@ -303,6 +310,8 @@ abstract class SettingsItem(
|
|||||||
BooleanSetting.FASTMEM_EXCLUSIVES.global = value
|
BooleanSetting.FASTMEM_EXCLUSIVES.global = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val isSaveable = true
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||||
getBoolean().toString()
|
getBoolean().toString()
|
||||||
|
|
||||||
|
@ -128,8 +128,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.custom) {
|
// Always load custom settings when launching a game from an intent
|
||||||
SettingsFile.loadCustomConfig(args.game!!)
|
if (args.custom || intentGame != null) {
|
||||||
|
SettingsFile.loadCustomConfig(game)
|
||||||
NativeConfig.unloadPerGameConfig()
|
NativeConfig.unloadPerGameConfig()
|
||||||
} else {
|
} else {
|
||||||
NativeConfig.reloadGlobalConfig()
|
NativeConfig.reloadGlobalConfig()
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -73,6 +74,8 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||||
@ -99,7 +102,8 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
|
|
||||||
reloadList()
|
reloadList()
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
homeViewModel.openImportSaves.collect {
|
homeViewModel.openImportSaves.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
@ -109,6 +113,17 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
homeViewModel.reloadPropertiesList.collect {
|
||||||
|
if (it) {
|
||||||
|
reloadList()
|
||||||
|
homeViewModel.reloadPropertiesList(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
@ -179,6 +194,7 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
InstallableProperty(
|
InstallableProperty(
|
||||||
R.string.save_data,
|
R.string.save_data,
|
||||||
R.string.save_data_description,
|
R.string.save_data_description,
|
||||||
|
R.drawable.ic_save,
|
||||||
{
|
{
|
||||||
MessageDialogFragment.newInstance(
|
MessageDialogFragment.newInstance(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
@ -214,7 +230,7 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
R.string.save_data_deleted_successfully,
|
R.string.save_data_deleted_successfully,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
reloadList()
|
homeViewModel.reloadPropertiesList(true)
|
||||||
}
|
}
|
||||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
@ -242,13 +258,20 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
requireActivity(),
|
||||||
|
titleId = R.string.clear_shader_cache,
|
||||||
|
descriptionId = R.string.clear_shader_cache_warning_description,
|
||||||
|
positiveAction = {
|
||||||
shaderCacheDir.deleteRecursively()
|
shaderCacheDir.deleteRecursively()
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
YuzuApplication.appContext,
|
YuzuApplication.appContext,
|
||||||
R.string.cleared_shaders_successfully,
|
R.string.cleared_shaders_successfully,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
reloadList()
|
homeViewModel.reloadPropertiesList(true)
|
||||||
|
}
|
||||||
|
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -388,7 +411,7 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
getString(R.string.save_file_imported_success),
|
getString(R.string.save_file_imported_success),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
reloadList()
|
homeViewModel.reloadPropertiesList(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheSaveDir.deleteRecursively()
|
cacheSaveDir.deleteRecursively()
|
||||||
|
@ -15,7 +15,7 @@ import org.yuzu.yuzu_emu.model.Game
|
|||||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
||||||
|
|
||||||
class LaunchGameDialogFragment : DialogFragment() {
|
class LaunchGameDialogFragment : DialogFragment() {
|
||||||
private var selectedItem = 0
|
private var selectedItem = 1
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val game = requireArguments().parcelable<Game>(GAME)
|
val game = requireArguments().parcelable<Game>(GAME)
|
||||||
@ -32,7 +32,7 @@ class LaunchGameDialogFragment : DialogFragment() {
|
|||||||
.actionGlobalEmulationActivity(game, selectedItem != 0)
|
.actionGlobalEmulationActivity(game, selectedItem != 0)
|
||||||
requireParentFragment().findNavController().navigate(action)
|
requireParentFragment().findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
.setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int ->
|
.setSingleChoiceItems(launchOptions, 1) { _: DialogInterface, i: Int ->
|
||||||
selectedItem = i
|
selectedItem = i
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
@ -10,17 +10,18 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
interface GameProperty {
|
interface GameProperty {
|
||||||
@get:StringRes
|
@get:StringRes
|
||||||
val titleId: Int
|
val titleId: Int
|
||||||
get() = -1
|
|
||||||
|
|
||||||
@get:StringRes
|
@get:StringRes
|
||||||
val descriptionId: Int
|
val descriptionId: Int
|
||||||
get() = -1
|
|
||||||
|
@get:DrawableRes
|
||||||
|
val iconId: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SubmenuProperty(
|
data class SubmenuProperty(
|
||||||
override val titleId: Int,
|
override val titleId: Int,
|
||||||
override val descriptionId: Int,
|
override val descriptionId: Int,
|
||||||
@DrawableRes val iconId: Int,
|
override val iconId: Int,
|
||||||
val details: (() -> String)? = null,
|
val details: (() -> String)? = null,
|
||||||
val detailsFlow: StateFlow<String>? = null,
|
val detailsFlow: StateFlow<String>? = null,
|
||||||
val action: () -> Unit
|
val action: () -> Unit
|
||||||
@ -29,6 +30,7 @@ data class SubmenuProperty(
|
|||||||
data class InstallableProperty(
|
data class InstallableProperty(
|
||||||
override val titleId: Int,
|
override val titleId: Int,
|
||||||
override val descriptionId: Int,
|
override val descriptionId: Int,
|
||||||
|
override val iconId: Int,
|
||||||
val install: (() -> Unit)? = null,
|
val install: (() -> Unit)? = null,
|
||||||
val export: (() -> Unit)? = null
|
val export: (() -> Unit)? = null
|
||||||
) : GameProperty
|
) : GameProperty
|
||||||
|
@ -28,6 +28,9 @@ class HomeViewModel : ViewModel() {
|
|||||||
private val _contentToInstall = MutableStateFlow<List<Uri>?>(null)
|
private val _contentToInstall = MutableStateFlow<List<Uri>?>(null)
|
||||||
val contentToInstall get() = _contentToInstall.asStateFlow()
|
val contentToInstall get() = _contentToInstall.asStateFlow()
|
||||||
|
|
||||||
|
private val _reloadPropertiesList = MutableStateFlow(false)
|
||||||
|
val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow()
|
||||||
|
|
||||||
var navigatedToSetup = false
|
var navigatedToSetup = false
|
||||||
|
|
||||||
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
|
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
|
||||||
@ -59,4 +62,8 @@ class HomeViewModel : ViewModel() {
|
|||||||
fun setContentToInstall(documents: List<Uri>?) {
|
fun setContentToInstall(documents: List<Uri>?) {
|
||||||
_contentToInstall.value = documents
|
_contentToInstall.value = documents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reloadPropertiesList(reload: Boolean) {
|
||||||
|
_reloadPropertiesList.value = reload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,18 +91,20 @@ class GamesFragment : Fragment() {
|
|||||||
viewLifecycleOwner.lifecycleScope.apply {
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
launch {
|
launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
|
gamesViewModel.isReloading.collect {
|
||||||
|
binding.swipeRefresh.isRefreshing = it
|
||||||
|
if (gamesViewModel.games.value.isEmpty() && !it) {
|
||||||
|
binding.noticeText.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.noticeText.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
gamesViewModel.games.collectLatest {
|
gamesViewModel.games.collectLatest {
|
||||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||||
if (it.isEmpty()) {
|
|
||||||
binding.noticeText.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
binding.noticeText.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,8 @@ object NativeConfig {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
external fun setGlobal(key: String, global: Boolean)
|
external fun setGlobal(key: String, global: Boolean)
|
||||||
|
|
||||||
|
external fun getIsSaveable(key: String): Boolean
|
||||||
|
|
||||||
external fun getDefaultToString(key: String): String
|
external fun getDefaultToString(key: String): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,13 +89,13 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fileType = loader->GetFileType();
|
const auto file_type = loader->GetFileType();
|
||||||
if (fileType == Loader::FileType::Unknown || fileType == Loader::FileType::Error) {
|
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 programId = 0;
|
u64 program_id = 0;
|
||||||
Loader::ResultStatus res = loader->ReadProgramId(programId);
|
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||||
if (res != Loader::ResultStatus::Success) {
|
if (res != Loader::ResultStatus::Success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ Core::System& EmulationSession::System() {
|
|||||||
return m_system;
|
return m_system;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSys::ManualContentProvider* EmulationSession::ContentProvider() {
|
FileSys::ManualContentProvider* EmulationSession::GetContentProvider() {
|
||||||
return m_manual_provider.get();
|
return m_manual_provider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,9 +296,6 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
|||||||
// Initialize filesystem.
|
// Initialize filesystem.
|
||||||
ConfigureFilesystemProvider(filepath);
|
ConfigureFilesystemProvider(filepath);
|
||||||
|
|
||||||
// Initialize account manager
|
|
||||||
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
|
|
||||||
|
|
||||||
// Load the ROM.
|
// Load the ROM.
|
||||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
|
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
|
||||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||||
@ -463,15 +460,13 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
|
|||||||
static_cast<jint>(result));
|
static_cast<jint>(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 EmulationSession::getProgramId(JNIEnv* env, jstring jprogramId) {
|
u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {
|
||||||
u64 program_id;
|
|
||||||
auto program_id_string = GetJString(env, jprogramId);
|
auto program_id_string = GetJString(env, jprogramId);
|
||||||
if (program_id_string.empty()) {
|
try {
|
||||||
program_id = 0;
|
return std::stoull(program_id_string);
|
||||||
} else {
|
} catch (...) {
|
||||||
program_id = std::stoull(program_id_string);
|
return 0;
|
||||||
}
|
}
|
||||||
return program_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||||
@ -526,16 +521,16 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
|
|||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
|
||||||
jstring jprogramId,
|
jstring jprogramId,
|
||||||
jstring jupdatePath) {
|
jstring jupdatePath) {
|
||||||
u64 programId = EmulationSession::getProgramId(env, jprogramId);
|
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
std::string updatePath = GetJString(env, jupdatePath);
|
std::string updatePath = GetJString(env, jupdatePath);
|
||||||
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
||||||
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath,
|
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath,
|
||||||
FileSys::Mode::Read));
|
FileSys::Mode::Read));
|
||||||
for (const auto& item : nsp->GetNCAs()) {
|
for (const auto& item : nsp->GetNCAs()) {
|
||||||
for (const auto& ncaDetails : item.second) {
|
for (const auto& nca_details : item.second) {
|
||||||
if (ncaDetails.second->GetName().ends_with(".cnmt.nca")) {
|
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
|
||||||
auto updateId = ncaDetails.second->GetTitleId() & ~0xFFFULL;
|
auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL;
|
||||||
if (updateId == programId) {
|
if (update_id == program_id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -770,8 +765,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
|||||||
auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
|
auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
|
||||||
Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
|
Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
|
||||||
|
|
||||||
Service::Account::ProfileManager manager;
|
const auto user_id = EmulationSession::GetInstance().System().GetProfileManager().GetUser(
|
||||||
const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
|
static_cast<std::size_t>(0));
|
||||||
ASSERT(user_id);
|
ASSERT(user_id);
|
||||||
|
|
||||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||||
@ -840,8 +835,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& system = EmulationSession::GetInstance().System();
|
auto& system = EmulationSession::GetInstance().System();
|
||||||
auto programId = EmulationSession::getProgramId(env, jprogramId);
|
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
const FileSys::PatchManager pm{programId, system.GetFileSystemController(),
|
const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
|
||||||
system.GetContentProvider()};
|
system.GetContentProvider()};
|
||||||
const auto loader = Loader::GetLoader(system, vFile);
|
const auto loader = Loader::GetLoader(system, vFile);
|
||||||
|
|
||||||
@ -849,11 +844,11 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
|
|||||||
loader->ReadUpdateRaw(update_raw);
|
loader->ReadUpdateRaw(update_raw);
|
||||||
|
|
||||||
auto addons = pm.GetPatchVersionNames(update_raw);
|
auto addons = pm.GetPatchVersionNames(update_raw);
|
||||||
auto emptyString = ToJString(env, "");
|
auto jemptyString = ToJString(env, "");
|
||||||
auto emptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||||
emptyString, emptyString);
|
jemptyString, jemptyString);
|
||||||
jobjectArray jaddonsArray =
|
jobjectArray jaddonsArray =
|
||||||
env->NewObjectArray(addons.size(), IDCache::GetPairClass(), emptyStringPair);
|
env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const auto& addon : addons) {
|
for (const auto& addon : addons) {
|
||||||
jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
|
||||||
@ -866,7 +861,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
|
|||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
|
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
|
||||||
jstring jprogramId) {
|
jstring jprogramId) {
|
||||||
auto programId = EmulationSession::getProgramId(env, jprogramId);
|
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
|
|
||||||
auto& system = EmulationSession::GetInstance().System();
|
auto& system = EmulationSession::GetInstance().System();
|
||||||
|
|
||||||
@ -881,7 +876,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
|
|||||||
|
|
||||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||||
system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
|
system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
|
||||||
programId, user_id->AsU128(), 0);
|
program_id, user_id->AsU128(), 0);
|
||||||
return ToJString(env, user_save_data_path);
|
return ToJString(env, user_save_data_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -891,7 +886,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
|
||||||
EmulationSession::GetInstance().ContentProvider()->ClearAllEntries();
|
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
@ -21,7 +21,7 @@ public:
|
|||||||
static EmulationSession& GetInstance();
|
static EmulationSession& GetInstance();
|
||||||
const Core::System& System() const;
|
const Core::System& System() const;
|
||||||
Core::System& System();
|
Core::System& System();
|
||||||
FileSys::ManualContentProvider* ContentProvider();
|
FileSys::ManualContentProvider* GetContentProvider();
|
||||||
|
|
||||||
const EmuWindow_Android& Window() const;
|
const EmuWindow_Android& Window() const;
|
||||||
EmuWindow_Android& Window();
|
EmuWindow_Android& Window();
|
||||||
@ -55,7 +55,7 @@ public:
|
|||||||
|
|
||||||
static void OnEmulationStarted();
|
static void OnEmulationStarted();
|
||||||
|
|
||||||
static u64 getProgramId(JNIEnv* env, jstring jprogramId);
|
static u64 GetProgramId(JNIEnv* env, jstring jprogramId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
||||||
@ -76,7 +76,6 @@ private:
|
|||||||
std::atomic<bool> m_is_running = false;
|
std::atomic<bool> m_is_running = false;
|
||||||
std::atomic<bool> m_is_paused = false;
|
std::atomic<bool> m_is_paused = false;
|
||||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
|
||||||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
||||||
|
|
||||||
// GPU driver parameters
|
// GPU driver parameters
|
||||||
|
@ -21,13 +21,13 @@ std::unique_ptr<AndroidConfig> per_game_config;
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||||
auto key = GetJString(env, jkey);
|
auto key = GetJString(env, jkey);
|
||||||
auto basicSetting = Settings::values.linkage.by_key[key];
|
auto basic_setting = Settings::values.linkage.by_key[key];
|
||||||
if (basicSetting != 0) {
|
if (basic_setting != 0) {
|
||||||
return static_cast<Settings::Setting<T>*>(basicSetting);
|
return static_cast<Settings::Setting<T>*>(basic_setting);
|
||||||
}
|
}
|
||||||
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
|
auto basic_android_setting = AndroidSettings::values.linkage.by_key[key];
|
||||||
if (basicAndroidSetting != 0) {
|
if (basic_android_setting != 0) {
|
||||||
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
|
return static_cast<Settings::Setting<T>*>(basic_android_setting);
|
||||||
}
|
}
|
||||||
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
|
LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -54,7 +54,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jo
|
|||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
|
||||||
jstring jprogramId,
|
jstring jprogramId,
|
||||||
jstring jfileName) {
|
jstring jfileName) {
|
||||||
auto program_id = EmulationSession::getProgramId(env, jprogramId);
|
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
auto file_name = GetJString(env, jfileName);
|
auto file_name = GetJString(env, jfileName);
|
||||||
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
|
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
|
||||||
per_game_config =
|
per_game_config =
|
||||||
@ -249,6 +249,15 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGlobal(JNIEnv* env, jobject o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsSaveable(JNIEnv* env, jobject obj,
|
||||||
|
jstring jkey) {
|
||||||
|
auto setting = getSetting<std::string>(env, jkey);
|
||||||
|
if (setting != nullptr) {
|
||||||
|
return setting->Save();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDefaultToString(JNIEnv* env, jobject obj,
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDefaultToString(JNIEnv* env, jobject obj,
|
||||||
jstring jkey) {
|
jstring jkey) {
|
||||||
auto setting = getSetting<std::string>(env, jkey);
|
auto setting = getSetting<std::string>(env, jkey);
|
||||||
@ -311,7 +320,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject
|
|||||||
|
|
||||||
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj,
|
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj,
|
||||||
jstring jprogramId) {
|
jstring jprogramId) {
|
||||||
auto program_id = EmulationSession::getProgramId(env, jprogramId);
|
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
auto& disabledAddons = Settings::values.disabled_addons[program_id];
|
auto& disabledAddons = Settings::values.disabled_addons[program_id];
|
||||||
jobjectArray jdisabledAddonsArray =
|
jobjectArray jdisabledAddonsArray =
|
||||||
env->NewObjectArray(disabledAddons.size(), IDCache::GetStringClass(), ToJString(env, ""));
|
env->NewObjectArray(disabledAddons.size(), IDCache::GetStringClass(), ToJString(env, ""));
|
||||||
@ -324,7 +333,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv
|
|||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
|
||||||
jstring jprogramId,
|
jstring jprogramId,
|
||||||
jobjectArray jdisabledAddons) {
|
jobjectArray jdisabledAddons) {
|
||||||
auto program_id = EmulationSession::getProgramId(env, jprogramId);
|
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||||
Settings::values.disabled_addons[program_id].clear();
|
Settings::values.disabled_addons[program_id].clear();
|
||||||
std::vector<std::string> disabled_addons;
|
std::vector<std::string> disabled_addons;
|
||||||
const int size = env->GetArrayLength(jdisabledAddons);
|
const int size = env->GetArrayLength(jdisabledAddons);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="960"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="960"
|
android:viewportHeight="24">
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="?attr/colorControlNormal"
|
||||||
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L647,120Q663,120 677.5,126Q692,132 703,143L817,257Q828,268 834,282.5Q840,297 840,313L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM760,314L646,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,314ZM480,720Q530,720 565,685Q600,650 600,600Q600,550 565,515Q530,480 480,480Q430,480 395,515Q360,550 360,600Q360,650 395,685Q430,720 480,720ZM280,400L560,400Q577,400 588.5,388.5Q600,377 600,360L600,280Q600,263 588.5,251.5Q577,240 560,240L280,240Q263,240 251.5,251.5Q240,263 240,280L240,360Q240,377 251.5,388.5Q263,400 280,400ZM200,314L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200L200,200L200,314Z"/>
|
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="?attr/materialCardViewOutlinedStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
app:tint="?attr/colorOnSurface"
|
||||||
|
tools:src="@drawable/ic_settings" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/user_data"
|
||||||
|
android:textAlignment="viewStart" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
style="@style/TextAppearance.Material3.BodyMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/user_data_description"
|
||||||
|
android:textAlignment="viewStart" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_export"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/export"
|
||||||
|
android:tooltipText="@string/export"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_export"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_install"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:contentDescription="@string/string_import"
|
||||||
|
android:tooltipText="@string/string_import"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_import"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="?attr/materialCardViewOutlinedStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
app:tint="?attr/colorOnSurface"
|
||||||
|
tools:src="@drawable/ic_settings" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/user_data"
|
||||||
|
android:textAlignment="viewStart" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
style="@style/TextAppearance.Material3.BodyMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/user_data_description"
|
||||||
|
android:textAlignment="viewStart" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_install"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/string_import"
|
||||||
|
android:tooltipText="@string/string_import"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_import"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_export"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:contentDescription="@string/export"
|
||||||
|
android:tooltipText="@string/export"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_export"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
@ -256,11 +256,13 @@
|
|||||||
|
|
||||||
<string-array name="outputEngineEntries">
|
<string-array name="outputEngineEntries">
|
||||||
<item>@string/auto</item>
|
<item>@string/auto</item>
|
||||||
|
<item>@string/oboe</item>
|
||||||
<item>@string/cubeb</item>
|
<item>@string/cubeb</item>
|
||||||
<item>@string/string_null</item>
|
<item>@string/string_null</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<integer-array name="outputEngineValues">
|
<integer-array name="outputEngineValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
|
<item>4</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<item>3</item>
|
<item>3</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
@ -314,6 +314,7 @@
|
|||||||
<string name="add_ons_description">Toggle mods, updates and DLC</string>
|
<string name="add_ons_description">Toggle mods, updates and DLC</string>
|
||||||
<string name="clear_shader_cache">Clear shader cache</string>
|
<string name="clear_shader_cache">Clear shader cache</string>
|
||||||
<string name="clear_shader_cache_description">Removes all shaders built while playing this game</string>
|
<string name="clear_shader_cache_description">Removes all shaders built while playing this game</string>
|
||||||
|
<string name="clear_shader_cache_warning_description">You will experience more stuttering as the shader cache regenerates</string>
|
||||||
<string name="cleared_shaders_successfully">Cleared shaders successfully</string>
|
<string name="cleared_shaders_successfully">Cleared shaders successfully</string>
|
||||||
<string name="addons_game">Addons: %1$s</string>
|
<string name="addons_game">Addons: %1$s</string>
|
||||||
<string name="save_data">Save data</string>
|
<string name="save_data">Save data</string>
|
||||||
@ -334,7 +335,7 @@
|
|||||||
<string name="addon_installed_successfully">Addon installed successfully</string>
|
<string name="addon_installed_successfully">Addon installed successfully</string>
|
||||||
<string name="verifying_content">Verifying content…</string>
|
<string name="verifying_content">Verifying content…</string>
|
||||||
<string name="content_install_notice">Content install notice</string>
|
<string name="content_install_notice">Content install notice</string>
|
||||||
<string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyways?</string>
|
<string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string>
|
||||||
|
|
||||||
<!-- ROM loading errors -->
|
<!-- ROM loading errors -->
|
||||||
<string name="loader_error_encrypted">Your ROM is encrypted</string>
|
<string name="loader_error_encrypted">Your ROM is encrypted</string>
|
||||||
@ -502,6 +503,7 @@
|
|||||||
<string name="theme_mode_dark">Dark</string>
|
<string name="theme_mode_dark">Dark</string>
|
||||||
|
|
||||||
<!-- Audio output engines -->
|
<!-- Audio output engines -->
|
||||||
|
<string name="oboe">oboe</string>
|
||||||
<string name="cubeb">cubeb</string>
|
<string name="cubeb">cubeb</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
|
@ -253,6 +253,17 @@ if (ENABLE_SDL2)
|
|||||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ANDROID)
|
||||||
|
target_sources(audio_core PRIVATE
|
||||||
|
sink/oboe_sink.cpp
|
||||||
|
sink/oboe_sink.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# FIXME: this port seems broken, it cannot be imported with find_package(oboe REQUIRED)
|
||||||
|
target_link_libraries(audio_core PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/liboboe.a")
|
||||||
|
target_compile_definitions(audio_core PRIVATE HAVE_OBOE)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (YUZU_USE_PRECOMPILED_HEADERS)
|
if (YUZU_USE_PRECOMPILED_HEADERS)
|
||||||
target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
|
target_precompile_headers(audio_core PRIVATE precompiled_headers.h)
|
||||||
endif()
|
endif()
|
||||||
|
@ -18,9 +18,7 @@ constexpr auto INCREMENT_TIME{5ms};
|
|||||||
DeviceSession::DeviceSession(Core::System& system_)
|
DeviceSession::DeviceSession(Core::System& system_)
|
||||||
: system{system_}, thread_event{Core::Timing::CreateEvent(
|
: system{system_}, thread_event{Core::Timing::CreateEvent(
|
||||||
"AudioOutSampleTick",
|
"AudioOutSampleTick",
|
||||||
[this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
|
[this](s64 time, std::chrono::nanoseconds) { return ThreadFunc(); })} {}
|
||||||
return ThreadFunc();
|
|
||||||
})} {}
|
|
||||||
|
|
||||||
DeviceSession::~DeviceSession() {
|
DeviceSession::~DeviceSession() {
|
||||||
Finalize();
|
Finalize();
|
||||||
|
@ -253,8 +253,9 @@ CubebSink::~CubebSink() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||||
const std::string& name, StreamType type) {
|
const std::string& name, StreamType type) {
|
||||||
|
system_channels = system_channels_;
|
||||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
||||||
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
||||||
|
|
||||||
|
223
src/audio_core/sink/oboe_sink.cpp
Normal file
223
src/audio_core/sink/oboe_sink.cpp
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <oboe/Oboe.h>
|
||||||
|
|
||||||
|
#include "audio_core/common/common.h"
|
||||||
|
#include "audio_core/sink/oboe_sink.h"
|
||||||
|
#include "audio_core/sink/sink_stream.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
|
||||||
|
namespace AudioCore::Sink {
|
||||||
|
|
||||||
|
class OboeSinkStream final : public SinkStream,
|
||||||
|
public oboe::AudioStreamDataCallback,
|
||||||
|
public oboe::AudioStreamErrorCallback {
|
||||||
|
public:
|
||||||
|
explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_,
|
||||||
|
u32 system_channels_)
|
||||||
|
: SinkStream(system_, type_) {
|
||||||
|
name = name_;
|
||||||
|
system_channels = system_channels_;
|
||||||
|
|
||||||
|
this->OpenStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
~OboeSinkStream() override {
|
||||||
|
LOG_INFO(Audio_Sink, "Destroyed Oboe stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Finalize() override {
|
||||||
|
this->Stop();
|
||||||
|
m_stream.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Start(bool resume = false) override {
|
||||||
|
if (!m_stream || !paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
paused = false;
|
||||||
|
|
||||||
|
if (m_stream->start() != oboe::Result::OK) {
|
||||||
|
LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() override {
|
||||||
|
if (!m_stream || paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->SignalPause();
|
||||||
|
|
||||||
|
if (m_stream->stop() != oboe::Result::OK) {
|
||||||
|
LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static s32 QueryChannelCount(oboe::Direction direction) {
|
||||||
|
std::shared_ptr<oboe::AudioStream> temp_stream;
|
||||||
|
oboe::AudioStreamBuilder builder;
|
||||||
|
|
||||||
|
const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream);
|
||||||
|
ASSERT(result == oboe::Result::OK);
|
||||||
|
|
||||||
|
return temp_stream->getChannelCount() >= 6 ? 6 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data,
|
||||||
|
s32 num_buffer_frames) override {
|
||||||
|
const size_t num_channels = this->GetDeviceChannels();
|
||||||
|
const size_t frame_size = num_channels;
|
||||||
|
const size_t num_frames = static_cast<size_t>(num_buffer_frames);
|
||||||
|
|
||||||
|
if (type == StreamType::In) {
|
||||||
|
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(audio_data),
|
||||||
|
num_frames * frame_size};
|
||||||
|
this->ProcessAudioIn(input_buffer, num_frames);
|
||||||
|
} else {
|
||||||
|
std::span<s16> output_buffer{reinterpret_cast<s16*>(audio_data),
|
||||||
|
num_frames * frame_size};
|
||||||
|
this->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oboe::DataCallbackResult::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override {
|
||||||
|
LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing");
|
||||||
|
|
||||||
|
if (this->OpenStream()) {
|
||||||
|
m_stream->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder,
|
||||||
|
oboe::Direction direction) {
|
||||||
|
// TODO: investigate callback delay issues when using AAudio
|
||||||
|
return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||||
|
->setAudioApi(oboe::AudioApi::OpenSLES)
|
||||||
|
->setDirection(direction)
|
||||||
|
->setSampleRate(TargetSampleRate)
|
||||||
|
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
|
||||||
|
->setFormat(oboe::AudioFormat::I16)
|
||||||
|
->setFormatConversionAllowed(true)
|
||||||
|
->setUsage(oboe::Usage::Game)
|
||||||
|
->setBufferCapacityInFrames(TargetSampleCount * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenStream() {
|
||||||
|
const auto direction = [&]() {
|
||||||
|
switch (type) {
|
||||||
|
case StreamType::In:
|
||||||
|
return oboe::Direction::Input;
|
||||||
|
case StreamType::Out:
|
||||||
|
case StreamType::Render:
|
||||||
|
return oboe::Direction::Output;
|
||||||
|
default:
|
||||||
|
ASSERT(false);
|
||||||
|
return oboe::Direction::Output;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
const auto expected_channels = QueryChannelCount(direction);
|
||||||
|
const auto expected_mask = [&]() {
|
||||||
|
switch (expected_channels) {
|
||||||
|
case 1:
|
||||||
|
return oboe::ChannelMask::Mono;
|
||||||
|
case 2:
|
||||||
|
return oboe::ChannelMask::Stereo;
|
||||||
|
case 6:
|
||||||
|
return oboe::ChannelMask::CM5Point1;
|
||||||
|
default:
|
||||||
|
ASSERT(false);
|
||||||
|
return oboe::ChannelMask::Unspecified;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
oboe::AudioStreamBuilder builder;
|
||||||
|
const auto result = ConfigureBuilder(builder, direction)
|
||||||
|
->setChannelCount(expected_channels)
|
||||||
|
->setChannelMask(expected_mask)
|
||||||
|
->setChannelConversionAllowed(true)
|
||||||
|
->setDataCallback(this)
|
||||||
|
->setErrorCallback(this)
|
||||||
|
->openStream(m_stream);
|
||||||
|
ASSERT(result == oboe::Result::OK);
|
||||||
|
return result == oboe::Result::OK && this->SetStreamProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetStreamProperties() {
|
||||||
|
ASSERT(m_stream);
|
||||||
|
|
||||||
|
m_stream->setBufferSizeInFrames(TargetSampleCount * 2);
|
||||||
|
device_channels = m_stream->getChannelCount();
|
||||||
|
|
||||||
|
const auto sample_rate = m_stream->getSampleRate();
|
||||||
|
const auto buffer_capacity = m_stream->getBufferCapacityInFrames();
|
||||||
|
const auto stream_backend =
|
||||||
|
m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES";
|
||||||
|
|
||||||
|
LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}",
|
||||||
|
stream_backend, device_channels, sample_rate, buffer_capacity);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<oboe::AudioStream> m_stream{};
|
||||||
|
};
|
||||||
|
|
||||||
|
OboeSink::OboeSink() {
|
||||||
|
// TODO: This is not generally knowable
|
||||||
|
// The channel count is distinct based on direction and can change
|
||||||
|
device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
OboeSink::~OboeSink() = default;
|
||||||
|
|
||||||
|
SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||||
|
const std::string& name, StreamType type) {
|
||||||
|
SinkStreamPtr& stream = sink_streams.emplace_back(
|
||||||
|
std::make_unique<OboeSinkStream>(system, type, name, system_channels));
|
||||||
|
|
||||||
|
return stream.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OboeSink::CloseStream(SinkStream* to_remove) {
|
||||||
|
sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void OboeSink::CloseStreams() {
|
||||||
|
sink_streams.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 OboeSink::GetDeviceVolume() const {
|
||||||
|
if (sink_streams.empty()) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sink_streams.front()->GetDeviceVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OboeSink::SetDeviceVolume(f32 volume) {
|
||||||
|
for (auto& stream : sink_streams) {
|
||||||
|
stream->SetDeviceVolume(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OboeSink::SetSystemVolume(f32 volume) {
|
||||||
|
for (auto& stream : sink_streams) {
|
||||||
|
stream->SetSystemVolume(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioCore::Sink
|
75
src/audio_core/sink/oboe_sink.h
Normal file
75
src/audio_core/sink/oboe_sink.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "audio_core/sink/sink.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AudioCore::Sink {
|
||||||
|
class SinkStream;
|
||||||
|
|
||||||
|
class OboeSink final : public Sink {
|
||||||
|
public:
|
||||||
|
explicit OboeSink();
|
||||||
|
~OboeSink() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new sink stream.
|
||||||
|
*
|
||||||
|
* @param system - Core system.
|
||||||
|
* @param system_channels - Number of channels the audio system expects.
|
||||||
|
* May differ from the device's channel count.
|
||||||
|
* @param name - Name of this stream.
|
||||||
|
* @param type - Type of this stream, render/in/out.
|
||||||
|
*
|
||||||
|
* @return A pointer to the created SinkStream
|
||||||
|
*/
|
||||||
|
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||||
|
const std::string& name, StreamType type) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a given stream.
|
||||||
|
*
|
||||||
|
* @param stream - The stream to close.
|
||||||
|
*/
|
||||||
|
void CloseStream(SinkStream* stream) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all streams.
|
||||||
|
*/
|
||||||
|
void CloseStreams() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device volume. Set from calls to the IAudioDevice service.
|
||||||
|
*
|
||||||
|
* @return Volume of the device.
|
||||||
|
*/
|
||||||
|
f32 GetDeviceVolume() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the device volume. Set from calls to the IAudioDevice service.
|
||||||
|
*
|
||||||
|
* @param volume - New volume of the device.
|
||||||
|
*/
|
||||||
|
void SetDeviceVolume(f32 volume) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the system volume. Comes from the audio system using this stream.
|
||||||
|
*
|
||||||
|
* @param volume - New volume of the system.
|
||||||
|
*/
|
||||||
|
void SetSystemVolume(f32 volume) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// List of streams managed by this sink
|
||||||
|
std::list<SinkStreamPtr> sink_streams{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace AudioCore::Sink
|
@ -168,8 +168,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
|
|||||||
|
|
||||||
SDLSink::~SDLSink() = default;
|
SDLSink::~SDLSink() = default;
|
||||||
|
|
||||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
|
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||||
const std::string&, StreamType type) {
|
const std::string&, StreamType type) {
|
||||||
|
system_channels = system_channels_;
|
||||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
||||||
device_channels, system_channels, output_device, input_device, type, system));
|
device_channels, system_channels, output_device, input_device, type, system));
|
||||||
return stream.get();
|
return stream.get();
|
||||||
|
@ -85,9 +85,21 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void SetSystemVolume(f32 volume) = 0;
|
virtual void SetSystemVolume(f32 volume) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of channels the game has set, can be different to the host hardware's support.
|
||||||
|
* Either 2 or 6.
|
||||||
|
*
|
||||||
|
* @return Number of device channels.
|
||||||
|
*/
|
||||||
|
u32 GetSystemChannels() const {
|
||||||
|
return system_channels;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Number of device channels supported by the hardware
|
/// Number of device channels supported by the hardware
|
||||||
u32 device_channels{2};
|
u32 device_channels{2};
|
||||||
|
/// Number of channels the game is sending
|
||||||
|
u32 system_channels{2};
|
||||||
};
|
};
|
||||||
|
|
||||||
using SinkPtr = std::unique_ptr<Sink>;
|
using SinkPtr = std::unique_ptr<Sink>;
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "audio_core/sink/sink_details.h"
|
#include "audio_core/sink/sink_details.h"
|
||||||
|
#ifdef HAVE_OBOE
|
||||||
|
#include "audio_core/sink/oboe_sink.h"
|
||||||
|
#endif
|
||||||
#ifdef HAVE_CUBEB
|
#ifdef HAVE_CUBEB
|
||||||
#include "audio_core/sink/cubeb_sink.h"
|
#include "audio_core/sink/cubeb_sink.h"
|
||||||
#endif
|
#endif
|
||||||
@ -36,6 +39,16 @@ struct SinkDetails {
|
|||||||
|
|
||||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||||
constexpr SinkDetails sink_details[] = {
|
constexpr SinkDetails sink_details[] = {
|
||||||
|
#ifdef HAVE_OBOE
|
||||||
|
SinkDetails{
|
||||||
|
Settings::AudioEngine::Oboe,
|
||||||
|
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||||
|
return std::make_unique<OboeSink>();
|
||||||
|
},
|
||||||
|
[](bool capture) { return std::vector<std::string>{"Default"}; },
|
||||||
|
[]() { return true; },
|
||||||
|
},
|
||||||
|
#endif
|
||||||
#ifdef HAVE_CUBEB
|
#ifdef HAVE_CUBEB
|
||||||
SinkDetails{
|
SinkDetails{
|
||||||
Settings::AudioEngine::Cubeb,
|
Settings::AudioEngine::Cubeb,
|
||||||
|
@ -40,29 +40,36 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
|
|||||||
|
|
||||||
if (system_channels == 6 && device_channels == 2) {
|
if (system_channels == 6 && device_channels == 2) {
|
||||||
// We're given 6 channels, but our device only outputs 2, so downmix.
|
// We're given 6 channels, but our device only outputs 2, so downmix.
|
||||||
static constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
|
// Front = 1.0
|
||||||
|
// Center = 0.596
|
||||||
|
// LFE = 0.354
|
||||||
|
// Back = 0.707
|
||||||
|
static constexpr std::array<f32, 4> down_mix_coeff{1.0, 0.596f, 0.354f, 0.707f};
|
||||||
|
|
||||||
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
|
for (u32 read_index = 0, write_index = 0; read_index < samples.size();
|
||||||
read_index += system_channels, write_index += device_channels) {
|
read_index += system_channels, write_index += device_channels) {
|
||||||
|
const auto fl =
|
||||||
|
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]);
|
||||||
|
const auto fr =
|
||||||
|
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]);
|
||||||
|
const auto c =
|
||||||
|
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::Center)]);
|
||||||
|
const auto lfe =
|
||||||
|
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::LFE)]);
|
||||||
|
const auto bl =
|
||||||
|
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackLeft)]);
|
||||||
|
const auto br =
|
||||||
|
static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackRight)]);
|
||||||
|
|
||||||
const auto left_sample{
|
const auto left_sample{
|
||||||
((Common::FixedPoint<49, 15>(
|
static_cast<s32>((fl * down_mix_coeff[0] + c * down_mix_coeff[1] +
|
||||||
samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
|
lfe * down_mix_coeff[2] + bl * down_mix_coeff[3]) *
|
||||||
down_mix_coeff[0] +
|
volume)};
|
||||||
samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
|
|
||||||
samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
|
|
||||||
samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
|
|
||||||
volume)
|
|
||||||
.to_int()};
|
|
||||||
|
|
||||||
const auto right_sample{
|
const auto right_sample{
|
||||||
((Common::FixedPoint<49, 15>(
|
static_cast<s32>((fr * down_mix_coeff[0] + c * down_mix_coeff[1] +
|
||||||
samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
|
lfe * down_mix_coeff[2] + br * down_mix_coeff[3]) *
|
||||||
down_mix_coeff[0] +
|
volume)};
|
||||||
samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
|
|
||||||
samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
|
|
||||||
samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
|
|
||||||
volume)
|
|
||||||
.to_int()};
|
|
||||||
|
|
||||||
samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
|
samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
|
||||||
static_cast<s16>(std::clamp(left_sample, min, max));
|
static_cast<s16>(std::clamp(left_sample, min, max));
|
||||||
|
@ -64,6 +64,8 @@ add_library(common STATIC
|
|||||||
fs/path_util.cpp
|
fs/path_util.cpp
|
||||||
fs/path_util.h
|
fs/path_util.h
|
||||||
hash.h
|
hash.h
|
||||||
|
heap_tracker.cpp
|
||||||
|
heap_tracker.h
|
||||||
hex_util.cpp
|
hex_util.cpp
|
||||||
hex_util.h
|
hex_util.h
|
||||||
host_memory.cpp
|
host_memory.cpp
|
||||||
|
@ -3,16 +3,19 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/logging/backend.h"
|
||||||
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
|
||||||
void assert_fail_impl() {
|
void assert_fail_impl() {
|
||||||
if (Settings::values.use_debug_asserts) {
|
if (Settings::values.use_debug_asserts) {
|
||||||
|
Common::Log::Stop();
|
||||||
Crash();
|
Crash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void unreachable_impl() {
|
[[noreturn]] void unreachable_impl() {
|
||||||
|
Common::Log::Stop();
|
||||||
Crash();
|
Crash();
|
||||||
throw std::runtime_error("Unreachable code");
|
throw std::runtime_error("Unreachable code");
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,12 @@ namespace Common {
|
|||||||
return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24;
|
return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g,
|
||||||
|
char h) {
|
||||||
|
return u64(a) << 0 | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 |
|
||||||
|
u64(g) << 48 | u64(h) << 56;
|
||||||
|
}
|
||||||
|
|
||||||
// std::size() does not support zero-size C arrays. We're fixing that.
|
// std::size() does not support zero-size C arrays. We're fixing that.
|
||||||
template <class C>
|
template <class C>
|
||||||
constexpr auto Size(const C& c) -> decltype(c.size()) {
|
constexpr auto Size(const C& c) -> decltype(c.size()) {
|
||||||
|
@ -354,18 +354,36 @@ std::string_view RemoveTrailingSlash(std::string_view path) {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> SplitPathComponents(std::string_view filename) {
|
template <typename F>
|
||||||
std::string copy(filename);
|
static void ForEachPathComponent(std::string_view filename, F&& cb) {
|
||||||
std::replace(copy.begin(), copy.end(), '\\', '/');
|
const char* component_begin = filename.data();
|
||||||
std::vector<std::string> out;
|
const char* const end = component_begin + filename.size();
|
||||||
|
for (const char* it = component_begin; it != end; ++it) {
|
||||||
std::stringstream stream(copy);
|
const char c = *it;
|
||||||
std::string item;
|
if (c == '\\' || c == '/') {
|
||||||
while (std::getline(stream, item, '/')) {
|
if (component_begin != it) {
|
||||||
out.push_back(std::move(item));
|
cb(std::string_view{component_begin, it});
|
||||||
}
|
}
|
||||||
|
component_begin = it + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (component_begin != end) {
|
||||||
|
cb(std::string_view{component_begin, end});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
std::vector<std::string_view> SplitPathComponents(std::string_view filename) {
|
||||||
|
std::vector<std::string_view> components;
|
||||||
|
ForEachPathComponent(filename, [&](auto component) { components.emplace_back(component); });
|
||||||
|
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SplitPathComponentsCopy(std::string_view filename) {
|
||||||
|
std::vector<std::string> components;
|
||||||
|
ForEachPathComponent(filename, [&](auto component) { components.emplace_back(component); });
|
||||||
|
|
||||||
|
return components;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
||||||
@ -400,9 +418,9 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
|
|||||||
return std::string(RemoveTrailingSlash(path));
|
return std::string(RemoveTrailingSlash(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view GetParentPath(std::string_view path) {
|
std::string GetParentPath(std::string_view path) {
|
||||||
if (path.empty()) {
|
if (path.empty()) {
|
||||||
return path;
|
return std::string(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
@ -421,7 +439,7 @@ std::string_view GetParentPath(std::string_view path) {
|
|||||||
name_index = std::max(name_bck_index, name_fwd_index);
|
name_index = std::max(name_bck_index, name_fwd_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.substr(0, name_index);
|
return std::string(path.substr(0, name_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view GetPathWithoutTop(std::string_view path) {
|
std::string_view GetPathWithoutTop(std::string_view path) {
|
||||||
|
@ -289,7 +289,11 @@ enum class DirectorySeparator {
|
|||||||
|
|
||||||
// Splits the path on '/' or '\' and put the components into a vector
|
// Splits the path on '/' or '\' and put the components into a vector
|
||||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||||
[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
|
[[nodiscard]] std::vector<std::string_view> SplitPathComponents(std::string_view filename);
|
||||||
|
|
||||||
|
// Splits the path on '/' or '\' and put the components into a vector
|
||||||
|
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||||
|
[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename);
|
||||||
|
|
||||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
||||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
||||||
@ -298,7 +302,7 @@ enum class DirectorySeparator {
|
|||||||
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
||||||
|
|
||||||
// Gets all of the text up to the last '/' or '\' in the path.
|
// Gets all of the text up to the last '/' or '\' in the path.
|
||||||
[[nodiscard]] std::string_view GetParentPath(std::string_view path);
|
[[nodiscard]] std::string GetParentPath(std::string_view path);
|
||||||
|
|
||||||
// Gets all of the text after the first '/' or '\' in the path.
|
// Gets all of the text after the first '/' or '\' in the path.
|
||||||
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
|
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
|
||||||
|
281
src/common/heap_tracker.cpp
Normal file
281
src/common/heap_tracker.cpp
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/heap_tracker.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
s64 GetMaxPermissibleResidentMapCount() {
|
||||||
|
// Default value.
|
||||||
|
s64 value = 65530;
|
||||||
|
|
||||||
|
// Try to read how many mappings we can make.
|
||||||
|
std::ifstream s("/proc/sys/vm/max_map_count");
|
||||||
|
s >> value;
|
||||||
|
|
||||||
|
// Print, for debug.
|
||||||
|
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
|
||||||
|
|
||||||
|
// Allow 20000 maps for other code and to account for split inaccuracy.
|
||||||
|
return std::max<s64>(value - 20000, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HeapTracker::HeapTracker(Common::HostMemory& buffer)
|
||||||
|
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
|
||||||
|
HeapTracker::~HeapTracker() = default;
|
||||||
|
|
||||||
|
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
||||||
|
MemoryPermission perm, bool is_separate_heap) {
|
||||||
|
// When mapping other memory, map pages immediately.
|
||||||
|
if (!is_separate_heap) {
|
||||||
|
m_buffer.Map(virtual_offset, host_offset, length, perm, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// We are mapping part of a separate heap.
|
||||||
|
std::scoped_lock lk{m_lock};
|
||||||
|
|
||||||
|
auto* const map = new SeparateHeapMap{
|
||||||
|
.vaddr = virtual_offset,
|
||||||
|
.paddr = host_offset,
|
||||||
|
.size = length,
|
||||||
|
.tick = m_tick++,
|
||||||
|
.perm = perm,
|
||||||
|
.is_resident = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert into mappings.
|
||||||
|
m_map_count++;
|
||||||
|
m_mappings.insert(*map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, map.
|
||||||
|
this->DeferredMapSeparateHeap(virtual_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_heap) {
|
||||||
|
// If this is a separate heap...
|
||||||
|
if (is_separate_heap) {
|
||||||
|
std::scoped_lock lk{m_lock};
|
||||||
|
|
||||||
|
const SeparateHeapMap key{
|
||||||
|
.vaddr = virtual_offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Split at the boundaries of the region we are removing.
|
||||||
|
this->SplitHeapMapLocked(virtual_offset);
|
||||||
|
this->SplitHeapMapLocked(virtual_offset + size);
|
||||||
|
|
||||||
|
// Erase all mappings in range.
|
||||||
|
auto it = m_mappings.find(key);
|
||||||
|
while (it != m_mappings.end() && it->vaddr < virtual_offset + size) {
|
||||||
|
// Get underlying item.
|
||||||
|
auto* const item = std::addressof(*it);
|
||||||
|
|
||||||
|
// If resident, erase from resident map.
|
||||||
|
if (item->is_resident) {
|
||||||
|
ASSERT(--m_resident_map_count >= 0);
|
||||||
|
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase from map.
|
||||||
|
ASSERT(--m_map_count >= 0);
|
||||||
|
it = m_mappings.erase(it);
|
||||||
|
|
||||||
|
// Free the item.
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap pages.
|
||||||
|
m_buffer.Unmap(virtual_offset, size, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeapTracker::Protect(size_t virtual_offset, size_t size, MemoryPermission perm) {
|
||||||
|
// Ensure no rebuild occurs while reprotecting.
|
||||||
|
std::shared_lock lk{m_rebuild_lock};
|
||||||
|
|
||||||
|
// Split at the boundaries of the region we are reprotecting.
|
||||||
|
this->SplitHeapMap(virtual_offset, size);
|
||||||
|
|
||||||
|
// Declare tracking variables.
|
||||||
|
const VAddr end = virtual_offset + size;
|
||||||
|
VAddr cur = virtual_offset;
|
||||||
|
|
||||||
|
while (cur < end) {
|
||||||
|
VAddr next = cur;
|
||||||
|
bool should_protect = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock lk2{m_lock};
|
||||||
|
|
||||||
|
const SeparateHeapMap key{
|
||||||
|
.vaddr = next,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to get the next mapping corresponding to this address.
|
||||||
|
const auto it = m_mappings.nfind(key);
|
||||||
|
|
||||||
|
if (it == m_mappings.end()) {
|
||||||
|
// There are no separate heap mappings remaining.
|
||||||
|
next = end;
|
||||||
|
should_protect = true;
|
||||||
|
} else if (it->vaddr == cur) {
|
||||||
|
// We are in range.
|
||||||
|
// Update permission bits.
|
||||||
|
it->perm = perm;
|
||||||
|
|
||||||
|
// Determine next address and whether we should protect.
|
||||||
|
next = cur + it->size;
|
||||||
|
should_protect = it->is_resident;
|
||||||
|
} else /* if (it->vaddr > cur) */ {
|
||||||
|
// We weren't in range, but there is a block coming up that will be.
|
||||||
|
next = it->vaddr;
|
||||||
|
should_protect = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to end.
|
||||||
|
next = std::min(next, end);
|
||||||
|
|
||||||
|
// Reprotect, if we need to.
|
||||||
|
if (should_protect) {
|
||||||
|
m_buffer.Protect(cur, next - cur, perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance.
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HeapTracker::DeferredMapSeparateHeap(u8* fault_address) {
|
||||||
|
if (m_buffer.IsInVirtualRange(fault_address)) {
|
||||||
|
return this->DeferredMapSeparateHeap(fault_address - m_buffer.VirtualBasePointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
|
||||||
|
bool rebuild_required = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock lk{m_lock};
|
||||||
|
|
||||||
|
// Check to ensure this was a non-resident separate heap mapping.
|
||||||
|
const auto it = this->GetNearestHeapMapLocked(virtual_offset);
|
||||||
|
if (it == m_mappings.end() || it->is_resident) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tick before possible rebuild.
|
||||||
|
it->tick = m_tick++;
|
||||||
|
|
||||||
|
// Check if we need to rebuild.
|
||||||
|
if (m_resident_map_count > m_max_resident_map_count) {
|
||||||
|
rebuild_required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the area.
|
||||||
|
m_buffer.Map(it->vaddr, it->paddr, it->size, it->perm, false);
|
||||||
|
|
||||||
|
// This map is now resident.
|
||||||
|
it->is_resident = true;
|
||||||
|
m_resident_map_count++;
|
||||||
|
m_resident_mappings.insert(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebuild_required) {
|
||||||
|
// A rebuild was required, so perform it now.
|
||||||
|
this->RebuildSeparateHeapAddressSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeapTracker::RebuildSeparateHeapAddressSpace() {
|
||||||
|
std::scoped_lock lk{m_rebuild_lock, m_lock};
|
||||||
|
|
||||||
|
ASSERT(!m_resident_mappings.empty());
|
||||||
|
|
||||||
|
// Dump half of the mappings.
|
||||||
|
//
|
||||||
|
// Despite being worse in theory, this has proven to be better in practice than more
|
||||||
|
// regularly dumping a smaller amount, because it significantly reduces average case
|
||||||
|
// lock contention.
|
||||||
|
const size_t desired_count = std::min(m_resident_map_count, m_max_resident_map_count) / 2;
|
||||||
|
const size_t evict_count = m_resident_map_count - desired_count;
|
||||||
|
auto it = m_resident_mappings.begin();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) {
|
||||||
|
// Unmark and unmap.
|
||||||
|
it->is_resident = false;
|
||||||
|
m_buffer.Unmap(it->vaddr, it->size, false);
|
||||||
|
|
||||||
|
// Advance.
|
||||||
|
ASSERT(--m_resident_map_count >= 0);
|
||||||
|
it = m_resident_mappings.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeapTracker::SplitHeapMap(VAddr offset, size_t size) {
|
||||||
|
std::scoped_lock lk{m_lock};
|
||||||
|
|
||||||
|
this->SplitHeapMapLocked(offset);
|
||||||
|
this->SplitHeapMapLocked(offset + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeapTracker::SplitHeapMapLocked(VAddr offset) {
|
||||||
|
const auto it = this->GetNearestHeapMapLocked(offset);
|
||||||
|
if (it == m_mappings.end() || it->vaddr == offset) {
|
||||||
|
// Not contained or no split required.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the original values.
|
||||||
|
auto* const left = std::addressof(*it);
|
||||||
|
const size_t orig_size = left->size;
|
||||||
|
|
||||||
|
// Adjust the left map.
|
||||||
|
const size_t left_size = offset - left->vaddr;
|
||||||
|
left->size = left_size;
|
||||||
|
|
||||||
|
// Create the new right map.
|
||||||
|
auto* const right = new SeparateHeapMap{
|
||||||
|
.vaddr = left->vaddr + left_size,
|
||||||
|
.paddr = left->paddr + left_size,
|
||||||
|
.size = orig_size - left_size,
|
||||||
|
.tick = left->tick,
|
||||||
|
.perm = left->perm,
|
||||||
|
.is_resident = left->is_resident,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert the new right map.
|
||||||
|
m_map_count++;
|
||||||
|
m_mappings.insert(*right);
|
||||||
|
|
||||||
|
// If resident, also insert into resident map.
|
||||||
|
if (right->is_resident) {
|
||||||
|
m_resident_map_count++;
|
||||||
|
m_resident_mappings.insert(*right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
|
||||||
|
const SeparateHeapMap key{
|
||||||
|
.vaddr = offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
return m_mappings.find(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
98
src/common/heap_tracker.h
Normal file
98
src/common/heap_tracker.h
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
#include "common/host_memory.h"
|
||||||
|
#include "common/intrusive_red_black_tree.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
struct SeparateHeapMap {
|
||||||
|
Common::IntrusiveRedBlackTreeNode addr_node{};
|
||||||
|
Common::IntrusiveRedBlackTreeNode tick_node{};
|
||||||
|
VAddr vaddr{};
|
||||||
|
PAddr paddr{};
|
||||||
|
size_t size{};
|
||||||
|
size_t tick{};
|
||||||
|
MemoryPermission perm{};
|
||||||
|
bool is_resident{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SeparateHeapMapAddrComparator {
|
||||||
|
static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) {
|
||||||
|
if (lhs.vaddr < rhs.vaddr) {
|
||||||
|
return -1;
|
||||||
|
} else if (lhs.vaddr <= (rhs.vaddr + rhs.size - 1)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SeparateHeapMapTickComparator {
|
||||||
|
static constexpr int Compare(const SeparateHeapMap& lhs, const SeparateHeapMap& rhs) {
|
||||||
|
if (lhs.tick < rhs.tick) {
|
||||||
|
return -1;
|
||||||
|
} else if (lhs.tick > rhs.tick) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return SeparateHeapMapAddrComparator::Compare(lhs, rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeapTracker {
|
||||||
|
public:
|
||||||
|
explicit HeapTracker(Common::HostMemory& buffer);
|
||||||
|
~HeapTracker();
|
||||||
|
|
||||||
|
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm,
|
||||||
|
bool is_separate_heap);
|
||||||
|
void Unmap(size_t virtual_offset, size_t size, bool is_separate_heap);
|
||||||
|
void Protect(size_t virtual_offset, size_t length, MemoryPermission perm);
|
||||||
|
u8* VirtualBasePointer() {
|
||||||
|
return m_buffer.VirtualBasePointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeferredMapSeparateHeap(u8* fault_address);
|
||||||
|
bool DeferredMapSeparateHeap(size_t virtual_offset);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using AddrTreeTraits =
|
||||||
|
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::addr_node>;
|
||||||
|
using AddrTree = AddrTreeTraits::TreeType<SeparateHeapMapAddrComparator>;
|
||||||
|
|
||||||
|
using TickTreeTraits =
|
||||||
|
Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&SeparateHeapMap::tick_node>;
|
||||||
|
using TickTree = TickTreeTraits::TreeType<SeparateHeapMapTickComparator>;
|
||||||
|
|
||||||
|
AddrTree m_mappings{};
|
||||||
|
TickTree m_resident_mappings{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SplitHeapMap(VAddr offset, size_t size);
|
||||||
|
void SplitHeapMapLocked(VAddr offset);
|
||||||
|
|
||||||
|
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
|
||||||
|
|
||||||
|
void RebuildSeparateHeapAddressSpace();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Common::HostMemory& m_buffer;
|
||||||
|
const s64 m_max_resident_map_count;
|
||||||
|
|
||||||
|
std::shared_mutex m_rebuild_lock{};
|
||||||
|
std::mutex m_lock{};
|
||||||
|
s64 m_map_count{};
|
||||||
|
s64 m_resident_map_count{};
|
||||||
|
size_t m_tick{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -11,10 +11,6 @@
|
|||||||
|
|
||||||
#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
|
#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
|
||||||
|
|
||||||
#ifdef ANDROID
|
|
||||||
#include <android/sharedmem.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef _GNU_SOURCE
|
#ifndef _GNU_SOURCE
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#endif
|
#endif
|
||||||
@ -193,6 +189,11 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ClearBackingRegion(size_t physical_offset, size_t length) {
|
||||||
|
// TODO: This does not seem to be possible on Windows.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void EnableDirectMappedAddress() {
|
void EnableDirectMappedAddress() {
|
||||||
// TODO
|
// TODO
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
@ -442,9 +443,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Backing memory initialization
|
// Backing memory initialization
|
||||||
#ifdef ANDROID
|
#if defined(__FreeBSD__) && __FreeBSD__ < 13
|
||||||
fd = ASharedMemory_create("HostMemory", backing_size);
|
|
||||||
#elif defined(__FreeBSD__) && __FreeBSD__ < 13
|
|
||||||
// XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
|
// XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
|
||||||
fd = shm_open(SHM_ANON, O_RDWR, 0600);
|
fd = shm_open(SHM_ANON, O_RDWR, 0600);
|
||||||
#else
|
#else
|
||||||
@ -455,7 +454,6 @@ public:
|
|||||||
throw std::bad_alloc{};
|
throw std::bad_alloc{};
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef ANDROID
|
|
||||||
// Defined to extend the file with zeros
|
// Defined to extend the file with zeros
|
||||||
int ret = ftruncate(fd, backing_size);
|
int ret = ftruncate(fd, backing_size);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
@ -463,7 +461,6 @@ public:
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
throw std::bad_alloc{};
|
throw std::bad_alloc{};
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
backing_base = static_cast<u8*>(
|
backing_base = static_cast<u8*>(
|
||||||
mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||||
@ -552,6 +549,19 @@ public:
|
|||||||
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
|
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ClearBackingRegion(size_t physical_offset, size_t length) {
|
||||||
|
#ifdef __linux__
|
||||||
|
// Set MADV_REMOVE on backing map to destroy it instantly.
|
||||||
|
// This also deletes the area from the backing file.
|
||||||
|
int ret = madvise(backing_base + physical_offset, length, MADV_REMOVE);
|
||||||
|
ASSERT_MSG(ret == 0, "madvise failed: {}", strerror(errno));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void EnableDirectMappedAddress() {
|
void EnableDirectMappedAddress() {
|
||||||
virtual_base = nullptr;
|
virtual_base = nullptr;
|
||||||
}
|
}
|
||||||
@ -623,6 +633,10 @@ public:
|
|||||||
|
|
||||||
void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {}
|
void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {}
|
||||||
|
|
||||||
|
bool ClearBackingRegion(size_t physical_offset, size_t length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void EnableDirectMappedAddress() {}
|
void EnableDirectMappedAddress() {}
|
||||||
|
|
||||||
u8* backing_base{nullptr};
|
u8* backing_base{nullptr};
|
||||||
@ -665,7 +679,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
|
|||||||
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
|
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
|
||||||
|
|
||||||
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
||||||
MemoryPermission perms) {
|
MemoryPermission perms, bool separate_heap) {
|
||||||
ASSERT(virtual_offset % PageAlignment == 0);
|
ASSERT(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(host_offset % PageAlignment == 0);
|
ASSERT(host_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
@ -677,7 +691,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
|||||||
impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms);
|
impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HostMemory::Unmap(size_t virtual_offset, size_t length) {
|
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
|
||||||
ASSERT(virtual_offset % PageAlignment == 0);
|
ASSERT(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
ASSERT(virtual_offset + length <= virtual_size);
|
||||||
@ -687,17 +701,25 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length) {
|
|||||||
impl->Unmap(virtual_offset + virtual_base_offset, length);
|
impl->Unmap(virtual_offset + virtual_base_offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write,
|
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
|
||||||
bool execute) {
|
|
||||||
ASSERT(virtual_offset % PageAlignment == 0);
|
ASSERT(virtual_offset % PageAlignment == 0);
|
||||||
ASSERT(length % PageAlignment == 0);
|
ASSERT(length % PageAlignment == 0);
|
||||||
ASSERT(virtual_offset + length <= virtual_size);
|
ASSERT(virtual_offset + length <= virtual_size);
|
||||||
if (length == 0 || !virtual_base || !impl) {
|
if (length == 0 || !virtual_base || !impl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bool read = True(perm & MemoryPermission::Read);
|
||||||
|
const bool write = True(perm & MemoryPermission::Write);
|
||||||
|
const bool execute = True(perm & MemoryPermission::Execute);
|
||||||
impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
|
impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) {
|
||||||
|
if (!impl || fill_value != 0 || !impl->ClearBackingRegion(physical_offset, length)) {
|
||||||
|
std::memset(backing_base + physical_offset, fill_value, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void HostMemory::EnableDirectMappedAddress() {
|
void HostMemory::EnableDirectMappedAddress() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->EnableDirectMappedAddress();
|
impl->EnableDirectMappedAddress();
|
||||||
|
@ -40,14 +40,17 @@ public:
|
|||||||
HostMemory(HostMemory&& other) noexcept;
|
HostMemory(HostMemory&& other) noexcept;
|
||||||
HostMemory& operator=(HostMemory&& other) noexcept;
|
HostMemory& operator=(HostMemory&& other) noexcept;
|
||||||
|
|
||||||
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms);
|
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms,
|
||||||
|
bool separate_heap);
|
||||||
|
|
||||||
void Unmap(size_t virtual_offset, size_t length);
|
void Unmap(size_t virtual_offset, size_t length, bool separate_heap);
|
||||||
|
|
||||||
void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute = false);
|
void Protect(size_t virtual_offset, size_t length, MemoryPermission perms);
|
||||||
|
|
||||||
void EnableDirectMappedAddress();
|
void EnableDirectMappedAddress();
|
||||||
|
|
||||||
|
void ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value);
|
||||||
|
|
||||||
[[nodiscard]] u8* BackingBasePointer() noexcept {
|
[[nodiscard]] u8* BackingBasePointer() noexcept {
|
||||||
return backing_base;
|
return backing_base;
|
||||||
}
|
}
|
||||||
@ -62,6 +65,10 @@ public:
|
|||||||
return virtual_base;
|
return virtual_base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsInVirtualRange(void* address) const noexcept {
|
||||||
|
return address >= virtual_base && address < virtual_base + virtual_size;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t backing_size{};
|
size_t backing_size{};
|
||||||
size_t virtual_size{};
|
size_t virtual_size{};
|
||||||
|
@ -208,6 +208,10 @@ public:
|
|||||||
instance->StartBackendThread();
|
instance->StartBackendThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Stop() {
|
||||||
|
instance->StopBackendThread();
|
||||||
|
}
|
||||||
|
|
||||||
Impl(const Impl&) = delete;
|
Impl(const Impl&) = delete;
|
||||||
Impl& operator=(const Impl&) = delete;
|
Impl& operator=(const Impl&) = delete;
|
||||||
|
|
||||||
@ -259,6 +263,15 @@ private:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StopBackendThread() {
|
||||||
|
backend_thread.request_stop();
|
||||||
|
if (backend_thread.joinable()) {
|
||||||
|
backend_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEachBackend([](Backend& backend) { backend.Flush(); });
|
||||||
|
}
|
||||||
|
|
||||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||||
const char* function, std::string&& message) const {
|
const char* function, std::string&& message) const {
|
||||||
using std::chrono::duration_cast;
|
using std::chrono::duration_cast;
|
||||||
@ -313,6 +326,10 @@ void Start() {
|
|||||||
Impl::Start();
|
Impl::Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
Impl::Stop();
|
||||||
|
}
|
||||||
|
|
||||||
void DisableLoggingInTests() {
|
void DisableLoggingInTests() {
|
||||||
initialization_in_progress_suppress_logging = true;
|
initialization_in_progress_suppress_logging = true;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@ void Initialize();
|
|||||||
|
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
|
/// Explicitly stops the logger thread and flushes the buffers
|
||||||
|
void Stop();
|
||||||
|
|
||||||
void DisableLoggingInTests();
|
void DisableLoggingInTests();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +103,7 @@ private:
|
|||||||
// Having them on the same cache-line would result in false-sharing between them.
|
// Having them on the same cache-line would result in false-sharing between them.
|
||||||
// TODO: Remove this ifdef whenever clang and GCC support
|
// TODO: Remove this ifdef whenever clang and GCC support
|
||||||
// std::hardware_destructive_interference_size.
|
// std::hardware_destructive_interference_size.
|
||||||
#if defined(_MSC_VER) && _MSC_VER >= 1911
|
#ifdef __cpp_lib_hardware_interference_size
|
||||||
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0};
|
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0};
|
||||||
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0};
|
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0};
|
||||||
#else
|
#else
|
||||||
|
@ -256,7 +256,7 @@ struct Values {
|
|||||||
AstcDecodeMode::CpuAsynchronous,
|
AstcDecodeMode::CpuAsynchronous,
|
||||||
"accelerate_astc",
|
"accelerate_astc",
|
||||||
Category::Renderer};
|
Category::Renderer};
|
||||||
Setting<VSyncMode, true> vsync_mode{
|
SwitchableSetting<VSyncMode, true> vsync_mode{
|
||||||
linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed,
|
linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed,
|
||||||
"use_vsync", Category::Renderer, Specialization::RuntimeList, true,
|
"use_vsync", Category::Renderer, Specialization::RuntimeList, true,
|
||||||
true};
|
true};
|
||||||
|
@ -82,16 +82,15 @@ enum class AudioEngine : u32 {
|
|||||||
Cubeb,
|
Cubeb,
|
||||||
Sdl2,
|
Sdl2,
|
||||||
Null,
|
Null,
|
||||||
|
Oboe,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline std::vector<std::pair<std::string, AudioEngine>>
|
inline std::vector<std::pair<std::string, AudioEngine>>
|
||||||
EnumMetadata<AudioEngine>::Canonicalizations() {
|
EnumMetadata<AudioEngine>::Canonicalizations() {
|
||||||
return {
|
return {
|
||||||
{"auto", AudioEngine::Auto},
|
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
|
||||||
{"cubeb", AudioEngine::Cubeb},
|
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
|
||||||
{"sdl2", AudioEngine::Sdl2},
|
|
||||||
{"null", AudioEngine::Null},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,6 +549,11 @@ add_library(core STATIC
|
|||||||
hle/service/hid/xcd.cpp
|
hle/service/hid/xcd.cpp
|
||||||
hle/service/hid/xcd.h
|
hle/service/hid/xcd.h
|
||||||
hle/service/hid/errors.h
|
hle/service/hid/errors.h
|
||||||
|
hle/service/hid/controllers/types/debug_pad_types.h
|
||||||
|
hle/service/hid/controllers/types/keyboard_types.h
|
||||||
|
hle/service/hid/controllers/types/mouse_types.h
|
||||||
|
hle/service/hid/controllers/types/npad_types.h
|
||||||
|
hle/service/hid/controllers/types/touch_types.h
|
||||||
hle/service/hid/controllers/applet_resource.cpp
|
hle/service/hid/controllers/applet_resource.cpp
|
||||||
hle/service/hid/controllers/applet_resource.h
|
hle/service/hid/controllers/applet_resource.h
|
||||||
hle/service/hid/controllers/console_six_axis.cpp
|
hle/service/hid/controllers/console_six_axis.cpp
|
||||||
@ -569,14 +574,15 @@ add_library(core STATIC
|
|||||||
hle/service/hid/controllers/palma.h
|
hle/service/hid/controllers/palma.h
|
||||||
hle/service/hid/controllers/seven_six_axis.cpp
|
hle/service/hid/controllers/seven_six_axis.cpp
|
||||||
hle/service/hid/controllers/seven_six_axis.h
|
hle/service/hid/controllers/seven_six_axis.h
|
||||||
|
hle/service/hid/controllers/shared_memory_format.h
|
||||||
|
hle/service/hid/controllers/shared_memory_holder.cpp
|
||||||
|
hle/service/hid/controllers/shared_memory_holder.h
|
||||||
hle/service/hid/controllers/six_axis.cpp
|
hle/service/hid/controllers/six_axis.cpp
|
||||||
hle/service/hid/controllers/six_axis.h
|
hle/service/hid/controllers/six_axis.h
|
||||||
hle/service/hid/controllers/stubbed.cpp
|
hle/service/hid/controllers/stubbed.cpp
|
||||||
hle/service/hid/controllers/stubbed.h
|
hle/service/hid/controllers/stubbed.h
|
||||||
hle/service/hid/controllers/touchscreen.cpp
|
hle/service/hid/controllers/touchscreen.cpp
|
||||||
hle/service/hid/controllers/touchscreen.h
|
hle/service/hid/controllers/touchscreen.h
|
||||||
hle/service/hid/controllers/xpad.cpp
|
|
||||||
hle/service/hid/controllers/xpad.h
|
|
||||||
hle/service/hid/hidbus/hidbus_base.cpp
|
hle/service/hid/hidbus/hidbus_base.cpp
|
||||||
hle/service/hid/hidbus/hidbus_base.h
|
hle/service/hid/hidbus/hidbus_base.h
|
||||||
hle/service/hid/hidbus/ringcon.cpp
|
hle/service/hid/hidbus/ringcon.cpp
|
||||||
@ -784,6 +790,12 @@ add_library(core STATIC
|
|||||||
hle/service/service.h
|
hle/service/service.h
|
||||||
hle/service/set/set.cpp
|
hle/service/set/set.cpp
|
||||||
hle/service/set/set.h
|
hle/service/set/set.h
|
||||||
|
hle/service/set/appln_settings.cpp
|
||||||
|
hle/service/set/appln_settings.h
|
||||||
|
hle/service/set/device_settings.cpp
|
||||||
|
hle/service/set/device_settings.h
|
||||||
|
hle/service/set/private_settings.cpp
|
||||||
|
hle/service/set/private_settings.h
|
||||||
hle/service/set/set_cal.cpp
|
hle/service/set/set_cal.cpp
|
||||||
hle/service/set/set_cal.h
|
hle/service/set/set_cal.h
|
||||||
hle/service/set/set_fd.cpp
|
hle/service/set/set_fd.cpp
|
||||||
@ -792,6 +804,8 @@ add_library(core STATIC
|
|||||||
hle/service/set/set_sys.h
|
hle/service/set/set_sys.h
|
||||||
hle/service/set/settings.cpp
|
hle/service/set/settings.cpp
|
||||||
hle/service/set/settings.h
|
hle/service/set/settings.h
|
||||||
|
hle/service/set/system_settings.cpp
|
||||||
|
hle/service/set/system_settings.h
|
||||||
hle/service/sm/sm.cpp
|
hle/service/sm/sm.cpp
|
||||||
hle/service/sm/sm.h
|
hle/service/sm/sm.h
|
||||||
hle/service/sm/sm_controller.cpp
|
hle/service/sm/sm_controller.cpp
|
||||||
@ -947,19 +961,24 @@ if (HAS_NCE)
|
|||||||
set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
|
set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
|
||||||
|
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
|
arm/nce/arm_nce_asm_definitions.h
|
||||||
arm/nce/arm_nce.cpp
|
arm/nce/arm_nce.cpp
|
||||||
arm/nce/arm_nce.h
|
arm/nce/arm_nce.h
|
||||||
arm/nce/arm_nce.s
|
arm/nce/arm_nce.s
|
||||||
arm/nce/guest_context.h
|
arm/nce/guest_context.h
|
||||||
|
arm/nce/instructions.h
|
||||||
|
arm/nce/interpreter_visitor.cpp
|
||||||
|
arm/nce/interpreter_visitor.h
|
||||||
arm/nce/patcher.cpp
|
arm/nce/patcher.cpp
|
||||||
arm/nce/patcher.h
|
arm/nce/patcher.h
|
||||||
arm/nce/instructions.h
|
arm/nce/visitor_base.h
|
||||||
)
|
)
|
||||||
target_link_libraries(core PRIVATE merry::oaknut)
|
target_link_libraries(core PRIVATE merry::mcl merry::oaknut)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||||
target_sources(core PRIVATE
|
target_sources(core PRIVATE
|
||||||
|
arm/dynarmic/arm_dynarmic.cpp
|
||||||
arm/dynarmic/arm_dynarmic.h
|
arm/dynarmic/arm_dynarmic.h
|
||||||
arm/dynarmic/arm_dynarmic_64.cpp
|
arm/dynarmic/arm_dynarmic_64.cpp
|
||||||
arm/dynarmic/arm_dynarmic_64.h
|
arm/dynarmic/arm_dynarmic_64.h
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
void ArmInterface::LogBacktrace(const Kernel::KProcess* process) const {
|
void ArmInterface::LogBacktrace(Kernel::KProcess* process) const {
|
||||||
Kernel::Svc::ThreadContext ctx;
|
Kernel::Svc::ThreadContext ctx;
|
||||||
this->GetContext(ctx);
|
this->GetContext(ctx);
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ public:
|
|||||||
virtual void SignalInterrupt(Kernel::KThread* thread) = 0;
|
virtual void SignalInterrupt(Kernel::KThread* thread) = 0;
|
||||||
|
|
||||||
// Stack trace generation.
|
// Stack trace generation.
|
||||||
void LogBacktrace(const Kernel::KProcess* process) const;
|
void LogBacktrace(Kernel::KProcess* process) const;
|
||||||
|
|
||||||
// Debug functionality.
|
// Debug functionality.
|
||||||
virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0;
|
virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0;
|
||||||
|
@ -79,7 +79,7 @@ constexpr std::array<u64, 2> SegmentBases{
|
|||||||
0x7100000000ULL,
|
0x7100000000ULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
void SymbolicateBacktrace(const Kernel::KProcess* process, std::vector<BacktraceEntry>& out) {
|
void SymbolicateBacktrace(Kernel::KProcess* process, std::vector<BacktraceEntry>& out) {
|
||||||
auto modules = FindModules(process);
|
auto modules = FindModules(process);
|
||||||
|
|
||||||
const bool is_64 = process->Is64Bit();
|
const bool is_64 = process->Is64Bit();
|
||||||
@ -118,7 +118,7 @@ void SymbolicateBacktrace(const Kernel::KProcess* process, std::vector<Backtrace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BacktraceEntry> GetAArch64Backtrace(const Kernel::KProcess* process,
|
std::vector<BacktraceEntry> GetAArch64Backtrace(Kernel::KProcess* process,
|
||||||
const Kernel::Svc::ThreadContext& ctx) {
|
const Kernel::Svc::ThreadContext& ctx) {
|
||||||
std::vector<BacktraceEntry> out;
|
std::vector<BacktraceEntry> out;
|
||||||
auto& memory = process->GetMemory();
|
auto& memory = process->GetMemory();
|
||||||
@ -144,7 +144,7 @@ std::vector<BacktraceEntry> GetAArch64Backtrace(const Kernel::KProcess* process,
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BacktraceEntry> GetAArch32Backtrace(const Kernel::KProcess* process,
|
std::vector<BacktraceEntry> GetAArch32Backtrace(Kernel::KProcess* process,
|
||||||
const Kernel::Svc::ThreadContext& ctx) {
|
const Kernel::Svc::ThreadContext& ctx) {
|
||||||
std::vector<BacktraceEntry> out;
|
std::vector<BacktraceEntry> out;
|
||||||
auto& memory = process->GetMemory();
|
auto& memory = process->GetMemory();
|
||||||
@ -173,7 +173,7 @@ std::vector<BacktraceEntry> GetAArch32Backtrace(const Kernel::KProcess* process,
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::optional<std::string> GetThreadName(const Kernel::KThread* thread) {
|
std::optional<std::string> GetThreadName(const Kernel::KThread* thread) {
|
||||||
const auto* process = thread->GetOwnerProcess();
|
auto* process = thread->GetOwnerProcess();
|
||||||
if (process->Is64Bit()) {
|
if (process->Is64Bit()) {
|
||||||
return GetNameFromThreadType64(process->GetMemory(), *thread);
|
return GetNameFromThreadType64(process->GetMemory(), *thread);
|
||||||
} else {
|
} else {
|
||||||
@ -248,7 +248,7 @@ Kernel::KProcessAddress GetModuleEnd(const Kernel::KProcess* process,
|
|||||||
return cur_addr - 1;
|
return cur_addr - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process) {
|
Loader::AppLoader::Modules FindModules(Kernel::KProcess* process) {
|
||||||
Loader::AppLoader::Modules modules;
|
Loader::AppLoader::Modules modules;
|
||||||
|
|
||||||
auto& page_table = process->GetPageTable();
|
auto& page_table = process->GetPageTable();
|
||||||
@ -312,7 +312,7 @@ Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process) {
|
|||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::KProcessAddress FindMainModuleEntrypoint(const Kernel::KProcess* process) {
|
Kernel::KProcessAddress FindMainModuleEntrypoint(Kernel::KProcess* process) {
|
||||||
// Do we have any loaded executable sections?
|
// Do we have any loaded executable sections?
|
||||||
auto modules = FindModules(process);
|
auto modules = FindModules(process);
|
||||||
|
|
||||||
@ -337,7 +337,7 @@ void InvalidateInstructionCacheRange(const Kernel::KProcess* process, u64 addres
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BacktraceEntry> GetBacktraceFromContext(const Kernel::KProcess* process,
|
std::vector<BacktraceEntry> GetBacktraceFromContext(Kernel::KProcess* process,
|
||||||
const Kernel::Svc::ThreadContext& ctx) {
|
const Kernel::Svc::ThreadContext& ctx) {
|
||||||
if (process->Is64Bit()) {
|
if (process->Is64Bit()) {
|
||||||
return GetAArch64Backtrace(process, ctx);
|
return GetAArch64Backtrace(process, ctx);
|
||||||
|
@ -14,9 +14,9 @@ std::optional<std::string> GetThreadName(const Kernel::KThread* thread);
|
|||||||
std::string_view GetThreadWaitReason(const Kernel::KThread* thread);
|
std::string_view GetThreadWaitReason(const Kernel::KThread* thread);
|
||||||
std::string GetThreadState(const Kernel::KThread* thread);
|
std::string GetThreadState(const Kernel::KThread* thread);
|
||||||
|
|
||||||
Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process);
|
Loader::AppLoader::Modules FindModules(Kernel::KProcess* process);
|
||||||
Kernel::KProcessAddress GetModuleEnd(const Kernel::KProcess* process, Kernel::KProcessAddress base);
|
Kernel::KProcessAddress GetModuleEnd(const Kernel::KProcess* process, Kernel::KProcessAddress base);
|
||||||
Kernel::KProcessAddress FindMainModuleEntrypoint(const Kernel::KProcess* process);
|
Kernel::KProcessAddress FindMainModuleEntrypoint(Kernel::KProcess* process);
|
||||||
|
|
||||||
void InvalidateInstructionCacheRange(const Kernel::KProcess* process, u64 address, u64 size);
|
void InvalidateInstructionCacheRange(const Kernel::KProcess* process, u64 address, u64 size);
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ struct BacktraceEntry {
|
|||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<BacktraceEntry> GetBacktraceFromContext(const Kernel::KProcess* process,
|
std::vector<BacktraceEntry> GetBacktraceFromContext(Kernel::KProcess* process,
|
||||||
const Kernel::Svc::ThreadContext& ctx);
|
const Kernel::Svc::ThreadContext& ctx);
|
||||||
std::vector<BacktraceEntry> GetBacktrace(const Kernel::KThread* thread);
|
std::vector<BacktraceEntry> GetBacktrace(const Kernel::KThread* thread);
|
||||||
|
|
||||||
|
49
src/core/arm/dynarmic/arm_dynarmic.cpp
Normal file
49
src/core/arm/dynarmic/arm_dynarmic.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
|
||||||
|
#include "common/signal_chain.h"
|
||||||
|
|
||||||
|
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
thread_local Core::Memory::Memory* g_current_memory{};
|
||||||
|
std::once_flag g_registered{};
|
||||||
|
struct sigaction g_old_segv {};
|
||||||
|
|
||||||
|
void HandleSigSegv(int sig, siginfo_t* info, void* ctx) {
|
||||||
|
if (g_current_memory && g_current_memory->InvalidateSeparateHeap(info->si_addr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_old_segv.sa_sigaction(sig, info, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ScopedJitExecution::ScopedJitExecution(Kernel::KProcess* process) {
|
||||||
|
g_current_memory = std::addressof(process->GetMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedJitExecution::~ScopedJitExecution() {
|
||||||
|
g_current_memory = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScopedJitExecution::RegisterHandler() {
|
||||||
|
std::call_once(g_registered, [] {
|
||||||
|
struct sigaction sa {};
|
||||||
|
sa.sa_sigaction = &HandleSigSegv;
|
||||||
|
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||||
|
Common::SigAction(SIGSEGV, std::addressof(sa), std::addressof(g_old_segv));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
|
|
||||||
|
#endif
|
@ -26,4 +26,24 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
|
|||||||
return static_cast<HaltReason>(hr);
|
return static_cast<HaltReason>(hr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
|
||||||
|
class ScopedJitExecution {
|
||||||
|
public:
|
||||||
|
explicit ScopedJitExecution(Kernel::KProcess* process);
|
||||||
|
~ScopedJitExecution();
|
||||||
|
static void RegisterHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
class ScopedJitExecution {
|
||||||
|
public:
|
||||||
|
explicit ScopedJitExecution(Kernel::KProcess* process) {}
|
||||||
|
~ScopedJitExecution() {}
|
||||||
|
static void RegisterHandler() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
@ -15,7 +15,7 @@ using namespace Common::Literals;
|
|||||||
|
|
||||||
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit DynarmicCallbacks32(ArmDynarmic32& parent, const Kernel::KProcess* process)
|
explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process)
|
||||||
: m_parent{parent}, m_memory(process->GetMemory()),
|
: m_parent{parent}, m_memory(process->GetMemory()),
|
||||||
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
|
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
|
||||||
m_check_memory_access{m_debugger_enabled ||
|
m_check_memory_access{m_debugger_enabled ||
|
||||||
@ -169,7 +169,7 @@ public:
|
|||||||
|
|
||||||
ArmDynarmic32& m_parent;
|
ArmDynarmic32& m_parent;
|
||||||
Core::Memory::Memory& m_memory;
|
Core::Memory::Memory& m_memory;
|
||||||
const Kernel::KProcess* m_process{};
|
Kernel::KProcess* m_process{};
|
||||||
const bool m_debugger_enabled{};
|
const bool m_debugger_enabled{};
|
||||||
const bool m_check_memory_access{};
|
const bool m_check_memory_access{};
|
||||||
static constexpr u64 MinimumRunCycles = 10000U;
|
static constexpr u64 MinimumRunCycles = 10000U;
|
||||||
@ -331,11 +331,15 @@ bool ArmDynarmic32::IsInThumbMode() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HaltReason ArmDynarmic32::RunThread(Kernel::KThread* thread) {
|
HaltReason ArmDynarmic32::RunThread(Kernel::KThread* thread) {
|
||||||
|
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||||
|
|
||||||
m_jit->ClearExclusiveState();
|
m_jit->ClearExclusiveState();
|
||||||
return TranslateHaltReason(m_jit->Run());
|
return TranslateHaltReason(m_jit->Run());
|
||||||
}
|
}
|
||||||
|
|
||||||
HaltReason ArmDynarmic32::StepThread(Kernel::KThread* thread) {
|
HaltReason ArmDynarmic32::StepThread(Kernel::KThread* thread) {
|
||||||
|
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||||
|
|
||||||
m_jit->ClearExclusiveState();
|
m_jit->ClearExclusiveState();
|
||||||
return TranslateHaltReason(m_jit->Step());
|
return TranslateHaltReason(m_jit->Step());
|
||||||
}
|
}
|
||||||
@ -370,13 +374,14 @@ void ArmDynarmic32::RewindBreakpointInstruction() {
|
|||||||
this->SetContext(m_breakpoint_context);
|
this->SetContext(m_breakpoint_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||||
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
|
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
|
||||||
m_cb(std::make_unique<DynarmicCallbacks32>(*this, process)),
|
m_cb(std::make_unique<DynarmicCallbacks32>(*this, process)),
|
||||||
m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index} {
|
m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index} {
|
||||||
auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl();
|
auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl();
|
||||||
m_jit = MakeJit(&page_table_impl);
|
m_jit = MakeJit(&page_table_impl);
|
||||||
|
ScopedJitExecution::RegisterHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
ArmDynarmic32::~ArmDynarmic32() = default;
|
ArmDynarmic32::~ArmDynarmic32() = default;
|
||||||
|
@ -20,7 +20,7 @@ class System;
|
|||||||
|
|
||||||
class ArmDynarmic32 final : public ArmInterface {
|
class ArmDynarmic32 final : public ArmInterface {
|
||||||
public:
|
public:
|
||||||
ArmDynarmic32(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||||
~ArmDynarmic32() override;
|
~ArmDynarmic32() override;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ using namespace Common::Literals;
|
|||||||
|
|
||||||
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit DynarmicCallbacks64(ArmDynarmic64& parent, const Kernel::KProcess* process)
|
explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process)
|
||||||
: m_parent{parent}, m_memory(process->GetMemory()),
|
: m_parent{parent}, m_memory(process->GetMemory()),
|
||||||
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
|
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
|
||||||
m_check_memory_access{m_debugger_enabled ||
|
m_check_memory_access{m_debugger_enabled ||
|
||||||
@ -216,7 +216,7 @@ public:
|
|||||||
Core::Memory::Memory& m_memory;
|
Core::Memory::Memory& m_memory;
|
||||||
u64 m_tpidrro_el0{};
|
u64 m_tpidrro_el0{};
|
||||||
u64 m_tpidr_el0{};
|
u64 m_tpidr_el0{};
|
||||||
const Kernel::KProcess* m_process{};
|
Kernel::KProcess* m_process{};
|
||||||
const bool m_debugger_enabled{};
|
const bool m_debugger_enabled{};
|
||||||
const bool m_check_memory_access{};
|
const bool m_check_memory_access{};
|
||||||
static constexpr u64 MinimumRunCycles = 10000U;
|
static constexpr u64 MinimumRunCycles = 10000U;
|
||||||
@ -362,11 +362,15 @@ std::shared_ptr<Dynarmic::A64::Jit> ArmDynarmic64::MakeJit(Common::PageTable* pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) {
|
HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) {
|
||||||
|
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||||
|
|
||||||
m_jit->ClearExclusiveState();
|
m_jit->ClearExclusiveState();
|
||||||
return TranslateHaltReason(m_jit->Run());
|
return TranslateHaltReason(m_jit->Run());
|
||||||
}
|
}
|
||||||
|
|
||||||
HaltReason ArmDynarmic64::StepThread(Kernel::KThread* thread) {
|
HaltReason ArmDynarmic64::StepThread(Kernel::KThread* thread) {
|
||||||
|
ScopedJitExecution sj(thread->GetOwnerProcess());
|
||||||
|
|
||||||
m_jit->ClearExclusiveState();
|
m_jit->ClearExclusiveState();
|
||||||
return TranslateHaltReason(m_jit->Step());
|
return TranslateHaltReason(m_jit->Step());
|
||||||
}
|
}
|
||||||
@ -399,13 +403,14 @@ void ArmDynarmic64::RewindBreakpointInstruction() {
|
|||||||
this->SetContext(m_breakpoint_context);
|
this->SetContext(m_breakpoint_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||||
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
|
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
|
||||||
m_cb(std::make_unique<DynarmicCallbacks64>(*this, process)), m_core_index{core_index} {
|
m_cb(std::make_unique<DynarmicCallbacks64>(*this, process)), m_core_index{core_index} {
|
||||||
auto& page_table = process->GetPageTable().GetBasePageTable();
|
auto& page_table = process->GetPageTable().GetBasePageTable();
|
||||||
auto& page_table_impl = page_table.GetImpl();
|
auto& page_table_impl = page_table.GetImpl();
|
||||||
m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth());
|
m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth());
|
||||||
|
ScopedJitExecution::RegisterHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
ArmDynarmic64::~ArmDynarmic64() = default;
|
ArmDynarmic64::~ArmDynarmic64() = default;
|
||||||
|
@ -25,7 +25,7 @@ class System;
|
|||||||
|
|
||||||
class ArmDynarmic64 final : public ArmInterface {
|
class ArmDynarmic64 final : public ArmInterface {
|
||||||
public:
|
public:
|
||||||
ArmDynarmic64(System& system, bool uses_wall_clock, const Kernel::KProcess* process,
|
ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process,
|
||||||
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||||
~ArmDynarmic64() override;
|
~ArmDynarmic64() override;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "common/signal_chain.h"
|
#include "common/signal_chain.h"
|
||||||
#include "core/arm/nce/arm_nce.h"
|
#include "core/arm/nce/arm_nce.h"
|
||||||
#include "core/arm/nce/guest_context.h"
|
#include "core/arm/nce/interpreter_visitor.h"
|
||||||
#include "core/arm/nce/patcher.h"
|
#include "core/arm/nce/patcher.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
@ -21,7 +21,8 @@ namespace Core {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct sigaction g_orig_action;
|
struct sigaction g_orig_bus_action;
|
||||||
|
struct sigaction g_orig_segv_action;
|
||||||
|
|
||||||
// Verify assembly offsets.
|
// Verify assembly offsets.
|
||||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||||
@ -37,6 +38,9 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
|
|||||||
return reinterpret_cast<fpsimd_context*>(header);
|
return reinterpret_cast<fpsimd_context*>(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using namespace Common::Literals;
|
||||||
|
constexpr u32 StackSize = 128_KiB;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
||||||
@ -104,19 +108,10 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
|||||||
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
|
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||||
|
|
||||||
// Try to handle an invalid access.
|
|
||||||
// TODO: handle accesses which split a page?
|
|
||||||
const Common::ProcessAddress addr =
|
|
||||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
|
||||||
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
|
||||||
// We handled the access successfully and are returning to guest code.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't handle the access, so determine why we crashed.
|
// We can't handle the access, so determine why we crashed.
|
||||||
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
||||||
|
|
||||||
@ -143,8 +138,44 @@ bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmNce::HandleHostFault(int sig, void* raw_info, void* raw_context) {
|
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||||
|
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||||
|
auto& memory = guest_ctx->system->ApplicationMemory();
|
||||||
|
|
||||||
|
// Match and execute an instruction.
|
||||||
|
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
|
||||||
|
if (next_pc) {
|
||||||
|
host_ctx.pc = *next_pc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We couldn't handle the access.
|
||||||
|
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||||
|
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||||
|
|
||||||
|
// Try to handle an invalid access.
|
||||||
|
// TODO: handle accesses which split a page?
|
||||||
|
const Common::ProcessAddress addr =
|
||||||
|
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||||
|
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||||
|
// We handled the access successfully and are returning to guest code.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We couldn't handle the access.
|
||||||
|
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) {
|
||||||
|
return g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) {
|
||||||
|
return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmNce::LockThread(Kernel::KThread* thread) {
|
void ArmNce::LockThread(Kernel::KThread* thread) {
|
||||||
@ -225,18 +256,31 @@ ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index)
|
|||||||
ArmNce::~ArmNce() = default;
|
ArmNce::~ArmNce() = default;
|
||||||
|
|
||||||
void ArmNce::Initialize() {
|
void ArmNce::Initialize() {
|
||||||
|
if (m_thread_id == -1) {
|
||||||
m_thread_id = gettid();
|
m_thread_id = gettid();
|
||||||
|
}
|
||||||
|
|
||||||
// Setup our signals
|
// Configure signal stack.
|
||||||
static std::once_flag signals;
|
if (!m_stack) {
|
||||||
std::call_once(signals, [] {
|
m_stack = std::make_unique<u8[]>(StackSize);
|
||||||
|
|
||||||
|
stack_t ss{};
|
||||||
|
ss.ss_sp = m_stack.get();
|
||||||
|
ss.ss_size = StackSize;
|
||||||
|
sigaltstack(&ss, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up signals.
|
||||||
|
static std::once_flag flag;
|
||||||
|
std::call_once(flag, [] {
|
||||||
using HandlerType = decltype(sigaction::sa_sigaction);
|
using HandlerType = decltype(sigaction::sa_sigaction);
|
||||||
|
|
||||||
sigset_t signal_mask;
|
sigset_t signal_mask;
|
||||||
sigemptyset(&signal_mask);
|
sigemptyset(&signal_mask);
|
||||||
sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
|
sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
|
||||||
sigaddset(&signal_mask, BreakFromRunCodeSignal);
|
sigaddset(&signal_mask, BreakFromRunCodeSignal);
|
||||||
sigaddset(&signal_mask, GuestFaultSignal);
|
sigaddset(&signal_mask, GuestAlignmentFaultSignal);
|
||||||
|
sigaddset(&signal_mask, GuestAccessFaultSignal);
|
||||||
|
|
||||||
struct sigaction return_to_run_code_action {};
|
struct sigaction return_to_run_code_action {};
|
||||||
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||||
@ -253,18 +297,19 @@ void ArmNce::Initialize() {
|
|||||||
break_from_run_code_action.sa_mask = signal_mask;
|
break_from_run_code_action.sa_mask = signal_mask;
|
||||||
Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
|
Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
|
||||||
|
|
||||||
struct sigaction fault_action {};
|
struct sigaction alignment_fault_action {};
|
||||||
fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
alignment_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||||
fault_action.sa_sigaction = reinterpret_cast<HandlerType>(&ArmNce::GuestFaultSignalHandler);
|
alignment_fault_action.sa_sigaction =
|
||||||
fault_action.sa_mask = signal_mask;
|
reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler);
|
||||||
Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action);
|
alignment_fault_action.sa_mask = signal_mask;
|
||||||
|
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr);
|
||||||
|
|
||||||
// Simplify call for g_orig_action.
|
struct sigaction access_fault_action {};
|
||||||
// These fields occupy the same space in memory, so this should be a no-op in practice.
|
access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||||
if (!(g_orig_action.sa_flags & SA_SIGINFO)) {
|
access_fault_action.sa_sigaction =
|
||||||
g_orig_action.sa_sigaction =
|
reinterpret_cast<HandlerType>(&ArmNce::GuestAccessFaultSignalHandler);
|
||||||
reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler);
|
access_fault_action.sa_mask = signal_mask;
|
||||||
}
|
Common::SigAction(GuestAccessFaultSignal, &access_fault_action, &g_orig_segv_action);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,8 @@ private:
|
|||||||
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
|
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
|
||||||
void* raw_context);
|
void* raw_context);
|
||||||
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
|
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
|
||||||
static void GuestFaultSignalHandler(int sig, void* info, void* raw_context);
|
static void GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||||
|
static void GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||||
|
|
||||||
static void LockThreadParameters(void* tpidr);
|
static void LockThreadParameters(void* tpidr);
|
||||||
static void UnlockThreadParameters(void* tpidr);
|
static void UnlockThreadParameters(void* tpidr);
|
||||||
@ -70,8 +71,11 @@ private:
|
|||||||
// C++ implementation functions for assembly definitions.
|
// C++ implementation functions for assembly definitions.
|
||||||
static void* RestoreGuestContext(void* raw_context);
|
static void* RestoreGuestContext(void* raw_context);
|
||||||
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
|
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
|
||||||
static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
||||||
static void HandleHostFault(int sig, void* info, void* raw_context);
|
static bool HandleGuestAlignmentFault(GuestContext* ctx, void* info, void* raw_context);
|
||||||
|
static bool HandleGuestAccessFault(GuestContext* ctx, void* info, void* raw_context);
|
||||||
|
static void HandleHostAlignmentFault(int sig, void* info, void* raw_context);
|
||||||
|
static void HandleHostAccessFault(int sig, void* info, void* raw_context);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
@ -83,6 +87,9 @@ public:
|
|||||||
// Core context.
|
// Core context.
|
||||||
GuestContext m_guest_ctx{};
|
GuestContext m_guest_ctx{};
|
||||||
Kernel::KThread* m_running_thread{};
|
Kernel::KThread* m_running_thread{};
|
||||||
|
|
||||||
|
// Stack for signal processing.
|
||||||
|
std::unique_ptr<u8[]> m_stack{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
@ -130,11 +130,11 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
/* static void Core::ArmNce::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */
|
/* static void Core::ArmNce::GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||||
.section .text._ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits
|
.section .text._ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||||
.global _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_
|
.global _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_
|
||||||
.type _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, %function
|
.type _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function
|
||||||
_ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
_ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
|
||||||
/* Check to see if we have the correct TLS magic. */
|
/* Check to see if we have the correct TLS magic. */
|
||||||
mrs x8, tpidr_el0
|
mrs x8, tpidr_el0
|
||||||
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
||||||
@ -146,7 +146,7 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
|||||||
|
|
||||||
/* Incorrect TLS magic, so this is a host fault. */
|
/* Incorrect TLS magic, so this is a host fault. */
|
||||||
/* Tail call the handler. */
|
/* Tail call the handler. */
|
||||||
b _ZN4Core6ArmNce15HandleHostFaultEiPvS1_
|
b _ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_
|
||||||
|
|
||||||
1:
|
1:
|
||||||
/* Correct TLS magic, so this is a guest fault. */
|
/* Correct TLS magic, so this is a guest fault. */
|
||||||
@ -163,7 +163,53 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_:
|
|||||||
msr tpidr_el0, x3
|
msr tpidr_el0, x3
|
||||||
|
|
||||||
/* Call the handler. */
|
/* Call the handler. */
|
||||||
bl _ZN4Core6ArmNce16HandleGuestFaultEPNS_12GuestContextEPvS3_
|
bl _ZN4Core6ArmNce25HandleGuestAlignmentFaultEPNS_12GuestContextEPvS3_
|
||||||
|
|
||||||
|
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
||||||
|
cbz x0, 2f
|
||||||
|
|
||||||
|
/* Otherwise, restore guest tpidr_el0. */
|
||||||
|
msr tpidr_el0, x19
|
||||||
|
|
||||||
|
2:
|
||||||
|
ldr x19, [sp, #0x10]
|
||||||
|
ldp x29, x30, [sp], #0x20
|
||||||
|
ret
|
||||||
|
|
||||||
|
/* static void Core::ArmNce::GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context) */
|
||||||
|
.section .text._ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, "ax", %progbits
|
||||||
|
.global _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_
|
||||||
|
.type _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, %function
|
||||||
|
_ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
|
||||||
|
/* Check to see if we have the correct TLS magic. */
|
||||||
|
mrs x8, tpidr_el0
|
||||||
|
ldr w9, [x8, #(TpidrEl0TlsMagic)]
|
||||||
|
|
||||||
|
LOAD_IMMEDIATE_32(w10, TlsMagic)
|
||||||
|
|
||||||
|
cmp w9, w10
|
||||||
|
b.eq 1f
|
||||||
|
|
||||||
|
/* Incorrect TLS magic, so this is a host fault. */
|
||||||
|
/* Tail call the handler. */
|
||||||
|
b _ZN4Core6ArmNce21HandleHostAccessFaultEiPvS1_
|
||||||
|
|
||||||
|
1:
|
||||||
|
/* Correct TLS magic, so this is a guest fault. */
|
||||||
|
stp x29, x30, [sp, #-0x20]!
|
||||||
|
str x19, [sp, #0x10]
|
||||||
|
mov x29, sp
|
||||||
|
|
||||||
|
/* Save the old tpidr_el0. */
|
||||||
|
mov x19, x8
|
||||||
|
|
||||||
|
/* Restore host tpidr_el0. */
|
||||||
|
ldr x0, [x8, #(TpidrEl0NativeContext)]
|
||||||
|
ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
|
||||||
|
msr tpidr_el0, x3
|
||||||
|
|
||||||
|
/* Call the handler. */
|
||||||
|
bl _ZN4Core6ArmNce22HandleGuestAccessFaultEPNS_12GuestContextEPvS3_
|
||||||
|
|
||||||
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
/* If the handler returned false, we want to preserve the host tpidr_el0. */
|
||||||
cbz x0, 2f
|
cbz x0, 2f
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
|
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
|
||||||
#define BreakFromRunCodeSignal SIGURG
|
#define BreakFromRunCodeSignal SIGURG
|
||||||
#define GuestFaultSignal SIGSEGV
|
#define GuestAccessFaultSignal SIGSEGV
|
||||||
|
#define GuestAlignmentFaultSignal SIGBUS
|
||||||
|
|
||||||
#define GuestContextSp 0xF8
|
#define GuestContextSp 0xF8
|
||||||
#define GuestContextHostContext 0x320
|
#define GuestContextHostContext 0x320
|
||||||
|
824
src/core/arm/nce/interpreter_visitor.cpp
Normal file
824
src/core/arm/nce/interpreter_visitor.cpp
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/bit_cast.h"
|
||||||
|
#include "core/arm/nce/interpreter_visitor.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
template <u32 BitSize>
|
||||||
|
u64 SignExtendToLong(u64 value) {
|
||||||
|
u64 mask = 1ULL << (BitSize - 1);
|
||||||
|
value &= (1ULL << BitSize) - 1;
|
||||||
|
return (value ^ mask) - mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 SignExtendToLong(u64 value, u64 bitsize) {
|
||||||
|
switch (bitsize) {
|
||||||
|
case 8:
|
||||||
|
return SignExtendToLong<8>(value);
|
||||||
|
case 16:
|
||||||
|
return SignExtendToLong<16>(value);
|
||||||
|
case 32:
|
||||||
|
return SignExtendToLong<32>(value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <u64 BitSize>
|
||||||
|
u32 SignExtendToWord(u32 value) {
|
||||||
|
u32 mask = 1ULL << (BitSize - 1);
|
||||||
|
value &= (1ULL << BitSize) - 1;
|
||||||
|
return (value ^ mask) - mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 SignExtendToWord(u32 value, u64 bitsize) {
|
||||||
|
switch (bitsize) {
|
||||||
|
case 8:
|
||||||
|
return SignExtendToWord<8>(value);
|
||||||
|
case 16:
|
||||||
|
return SignExtendToWord<16>(value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 SignExtend(u64 value, u64 bitsize, u64 regsize) {
|
||||||
|
if (regsize == 64) {
|
||||||
|
return SignExtendToLong(value, bitsize);
|
||||||
|
} else {
|
||||||
|
return SignExtendToWord(static_cast<u32>(value), bitsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u128 VectorGetElement(u128 value, u64 bitsize) {
|
||||||
|
switch (bitsize) {
|
||||||
|
case 8:
|
||||||
|
return {value[0] & ((1ULL << 8) - 1), 0};
|
||||||
|
case 16:
|
||||||
|
return {value[0] & ((1ULL << 16) - 1), 0};
|
||||||
|
case 32:
|
||||||
|
return {value[0] & ((1ULL << 32) - 1), 0};
|
||||||
|
case 64:
|
||||||
|
return {value[0], 0};
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift) {
|
||||||
|
ASSERT(shift <= 4);
|
||||||
|
ASSERT(bitsize == 32 || bitsize == 64);
|
||||||
|
u64 val = this->GetReg(reg);
|
||||||
|
size_t len;
|
||||||
|
u64 extended;
|
||||||
|
bool signed_extend;
|
||||||
|
|
||||||
|
switch (option.ZeroExtend()) {
|
||||||
|
case 0b000: { // UXTB
|
||||||
|
val &= ((1ULL << 8) - 1);
|
||||||
|
len = 8;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b001: { // UXTH
|
||||||
|
val &= ((1ULL << 16) - 1);
|
||||||
|
len = 16;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b010: { // UXTW
|
||||||
|
val &= ((1ULL << 32) - 1);
|
||||||
|
len = 32;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b011: { // UXTX
|
||||||
|
len = 64;
|
||||||
|
signed_extend = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b100: { // SXTB
|
||||||
|
val &= ((1ULL << 8) - 1);
|
||||||
|
len = 8;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b101: { // SXTH
|
||||||
|
val &= ((1ULL << 16) - 1);
|
||||||
|
len = 16;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b110: { // SXTW
|
||||||
|
val &= ((1ULL << 32) - 1);
|
||||||
|
len = 32;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0b111: { // SXTX
|
||||||
|
len = 64;
|
||||||
|
signed_extend = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < bitsize && signed_extend) {
|
||||||
|
extended = SignExtend(val, len, bitsize);
|
||||||
|
} else {
|
||||||
|
extended = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extended << shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
u128 InterpreterVisitor::GetVec(Vec v) {
|
||||||
|
return m_fpsimd_regs[static_cast<u32>(v)];
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::GetReg(Reg r) {
|
||||||
|
return m_regs[static_cast<u32>(r)];
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::GetSp() {
|
||||||
|
return m_sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InterpreterVisitor::GetPc() {
|
||||||
|
return m_pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpreterVisitor::SetVec(Vec v, u128 value) {
|
||||||
|
m_fpsimd_regs[static_cast<u32>(v)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpreterVisitor::SetReg(Reg r, u64 value) {
|
||||||
|
m_regs[static_cast<u32>(r)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpreterVisitor::SetSp(u64 value) {
|
||||||
|
m_sp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt) {
|
||||||
|
const auto memop = L ? MemOp::Load : MemOp::Store;
|
||||||
|
const size_t elsize = 8 << size;
|
||||||
|
const size_t datasize = elsize;
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
const size_t dbytes = datasize / 8;
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
|
u64 value = this->GetReg(Rt);
|
||||||
|
m_memory.WriteBlock(address, &value, dbytes);
|
||||||
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 value = 0;
|
||||||
|
m_memory.ReadBlock(address, &value, dbytes);
|
||||||
|
this->SetReg(Rt, value);
|
||||||
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STLLR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 0;
|
||||||
|
const bool o0 = 0;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STLR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 0;
|
||||||
|
const bool o0 = 1;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDLAR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 1;
|
||||||
|
const bool o0 = 0;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDAR(Imm<2> sz, Reg Rn, Reg Rt) {
|
||||||
|
const size_t size = sz.ZeroExtend<size_t>();
|
||||||
|
const bool L = 1;
|
||||||
|
const bool o0 = 1;
|
||||||
|
return this->Ordered(size, L, o0, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) {
|
||||||
|
const size_t size = opc_0 == 0 ? 4 : 8;
|
||||||
|
const s64 offset = Dynarmic::concatenate(imm19, Imm<2>{0}).SignExtend<s64>();
|
||||||
|
const u64 address = this->GetPc() + offset;
|
||||||
|
|
||||||
|
u64 data = 0;
|
||||||
|
m_memory.ReadBlock(address, &data, size);
|
||||||
|
|
||||||
|
this->SetReg(Rt, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) {
|
||||||
|
if (opc == 0b11) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size in bytes
|
||||||
|
const u64 size = 4 << opc.ZeroExtend();
|
||||||
|
const u64 offset = imm19.SignExtend<u64>() << 2;
|
||||||
|
const u64 address = this->GetPc() + offset;
|
||||||
|
|
||||||
|
u128 data{};
|
||||||
|
m_memory.ReadBlock(address, &data, size);
|
||||||
|
this->SetVec(Vt, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L,
|
||||||
|
Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt) {
|
||||||
|
if ((L == 0 && opc.Bit<0>() == 1) || opc == 0b11) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto memop = L == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
if (memop == MemOp::Load && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memop == MemOp::Store && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memop == MemOp::Load && Rt == Rt2) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const bool signed_ = opc.Bit<0>() != 0;
|
||||||
|
const size_t scale = 2 + opc.Bit<1>();
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
const u64 offset = imm7.SignExtend<u64>() << scale;
|
||||||
|
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t dbytes = datasize / 8;
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u64 data1 = this->GetReg(Rt);
|
||||||
|
u64 data2 = this->GetReg(Rt2);
|
||||||
|
m_memory.WriteBlock(address, &data1, dbytes);
|
||||||
|
m_memory.WriteBlock(address + dbytes, &data2, dbytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 data1 = 0, data2 = 0;
|
||||||
|
m_memory.ReadBlock(address, &data1, dbytes);
|
||||||
|
m_memory.ReadBlock(address + dbytes, &data2, dbytes);
|
||||||
|
if (signed_) {
|
||||||
|
this->SetReg(Rt, SignExtend(data1, datasize, 64));
|
||||||
|
this->SetReg(Rt2, SignExtend(data2, datasize, 64));
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rt, data1);
|
||||||
|
this->SetReg(Rt2, data2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L,
|
||||||
|
Imm<7> imm7, Vec Vt2, Reg Rn, Vec Vt) {
|
||||||
|
if (opc == 0b11) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto memop = L == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
if (memop == MemOp::Load && Vt == Vt2) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const size_t scale = 2 + opc.ZeroExtend<size_t>();
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
const u64 offset = imm7.SignExtend<u64>() << scale;
|
||||||
|
const size_t dbytes = datasize / 8;
|
||||||
|
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u128 data1 = VectorGetElement(this->GetVec(Vt), datasize);
|
||||||
|
u128 data2 = VectorGetElement(this->GetVec(Vt2), datasize);
|
||||||
|
m_memory.WriteBlock(address, &data1, dbytes);
|
||||||
|
m_memory.WriteBlock(address + dbytes, &data2, dbytes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u128 data1{}, data2{};
|
||||||
|
m_memory.ReadBlock(address, &data1, dbytes);
|
||||||
|
m_memory.ReadBlock(address + dbytes, &data2, dbytes);
|
||||||
|
this->SetVec(Vt, data1);
|
||||||
|
this->SetVec(Vt2, data2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset,
|
||||||
|
Imm<2> size, Imm<2> opc, Reg Rn, Reg Rt) {
|
||||||
|
MemOp memop;
|
||||||
|
bool signed_ = false;
|
||||||
|
size_t regsize = 0;
|
||||||
|
|
||||||
|
if (opc.Bit<1>() == 0) {
|
||||||
|
memop = opc.Bit<0>() ? MemOp::Load : MemOp::Store;
|
||||||
|
regsize = size == 0b11 ? 64 : 32;
|
||||||
|
signed_ = false;
|
||||||
|
} else if (size == 0b11) {
|
||||||
|
memop = MemOp::Prefetch;
|
||||||
|
ASSERT(!opc.Bit<0>());
|
||||||
|
} else {
|
||||||
|
memop = MemOp::Load;
|
||||||
|
ASSERT(!(size == 0b10 && opc.Bit<0>() == 1));
|
||||||
|
regsize = opc.Bit<0>() ? 32 : 64;
|
||||||
|
signed_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memop == MemOp::Load && wback && Rn == Rt && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (memop == MemOp::Store && wback && Rn == Rt && Rn != Reg::R31) {
|
||||||
|
// Unpredictable instruction
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u64 data = this->GetReg(Rt);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 data = 0;
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
if (signed_) {
|
||||||
|
this->SetReg(Rt, SignExtend(data, datasize, regsize));
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rt, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Prefetch:
|
||||||
|
// this->Prefetch(address, Rt)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex,
|
||||||
|
Reg Rn, Reg Rt) {
|
||||||
|
const bool wback = true;
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) {
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||||
|
|
||||||
|
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) {
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset,
|
||||||
|
MemOp memop, Reg Rn, Vec Vt) {
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u128 data = VectorGetElement(this->GetVec(Vt), datasize);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u128 data{};
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
this->SetVec(Vt, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wback) {
|
||||||
|
if (postindex) {
|
||||||
|
address += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
this->SetSp(address);
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rn, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9,
|
||||||
|
bool not_postindex, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = true;
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn,
|
||||||
|
Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9,
|
||||||
|
bool not_postindex, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = true;
|
||||||
|
const bool postindex = !not_postindex;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn,
|
||||||
|
Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm12.ZeroExtend<u64>() << scale;
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) {
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wback = false;
|
||||||
|
const bool postindex = false;
|
||||||
|
const u64 offset = imm9.SignExtend<u64>();
|
||||||
|
|
||||||
|
return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1,
|
||||||
|
Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Reg Rt) {
|
||||||
|
MemOp memop;
|
||||||
|
size_t regsize = 64;
|
||||||
|
bool signed_ = false;
|
||||||
|
|
||||||
|
if (opc_1 == 0) {
|
||||||
|
memop = opc_0 == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
regsize = size == 0b11 ? 64 : 32;
|
||||||
|
signed_ = false;
|
||||||
|
} else if (size == 0b11) {
|
||||||
|
memop = MemOp::Prefetch;
|
||||||
|
if (opc_0 == 1) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memop = MemOp::Load;
|
||||||
|
if (size == 0b10 && opc_0 == 1) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
regsize = opc_0 == 1 ? 32 : 64;
|
||||||
|
signed_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
const u64 offset = this->ExtendReg(64, Rm, option, shift);
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
address += offset;
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u64 data = this->GetReg(Rt);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u64 data = 0;
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
if (signed_) {
|
||||||
|
this->SetReg(Rt, SignExtend(data, datasize, regsize));
|
||||||
|
} else {
|
||||||
|
this->SetReg(Rt, data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Prefetch:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) {
|
||||||
|
const Imm<1> opc_0{0};
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) {
|
||||||
|
const Imm<1> opc_0{1};
|
||||||
|
const size_t scale = size.ZeroExtend<size_t>();
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option,
|
||||||
|
Reg Rn, Vec Vt) {
|
||||||
|
const auto memop = opc_0 == 1 ? MemOp::Load : MemOp::Store;
|
||||||
|
const size_t datasize = 8 << scale;
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
const u64 offset = this->ExtendReg(64, Rm, option, shift);
|
||||||
|
|
||||||
|
u64 address;
|
||||||
|
if (Rn == Reg::SP) {
|
||||||
|
address = this->GetSp();
|
||||||
|
} else {
|
||||||
|
address = this->GetReg(Rn);
|
||||||
|
}
|
||||||
|
address += offset;
|
||||||
|
|
||||||
|
switch (memop) {
|
||||||
|
case MemOp::Store: {
|
||||||
|
u128 data = VectorGetElement(this->GetVec(Vt), datasize);
|
||||||
|
m_memory.WriteBlock(address, &data, datasize / 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MemOp::Load: {
|
||||||
|
u128 data{};
|
||||||
|
m_memory.ReadBlock(address, &data, datasize / 8);
|
||||||
|
this->SetVec(Vt, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S,
|
||||||
|
Reg Rn, Vec Vt) {
|
||||||
|
const Imm<1> opc_0{0};
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S,
|
||||||
|
Reg Rn, Vec Vt) {
|
||||||
|
const Imm<1> opc_0{1};
|
||||||
|
const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>();
|
||||||
|
if (scale > 4) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u8 shift = S ? static_cast<u8>(scale) : 0;
|
||||||
|
if (!option.Bit<1>()) {
|
||||||
|
// Unallocated encoding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||||
|
fpsimd_context* fpsimd_context) {
|
||||||
|
// Construct the interpreter.
|
||||||
|
std::span<u64, 31> regs(reinterpret_cast<u64*>(context->regs), 31);
|
||||||
|
std::span<u128, 32> vregs(reinterpret_cast<u128*>(fpsimd_context->vregs), 32);
|
||||||
|
u64& sp = *reinterpret_cast<u64*>(&context->sp);
|
||||||
|
const u64& pc = *reinterpret_cast<u64*>(&context->pc);
|
||||||
|
|
||||||
|
InterpreterVisitor visitor(memory, regs, vregs, sp, pc);
|
||||||
|
|
||||||
|
// Read the instruction at the program counter.
|
||||||
|
u32 instruction = memory.Read32(pc);
|
||||||
|
bool was_executed = false;
|
||||||
|
|
||||||
|
// Interpret the instruction.
|
||||||
|
if (auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction)) {
|
||||||
|
was_executed = decoder->get().call(visitor, instruction);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Core_ARM, "Unallocated encoding: {:#x}", instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_executed) {
|
||||||
|
return pc + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
103
src/core/arm/nce/interpreter_visitor.h
Normal file
103
src/core/arm/nce/interpreter_visitor.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "core/arm/nce/visitor_base.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
namespace Memory {
|
||||||
|
class Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InterpreterVisitor final : public VisitorBase {
|
||||||
|
public:
|
||||||
|
explicit InterpreterVisitor(Core::Memory::Memory& memory, std::span<u64, 31> regs,
|
||||||
|
std::span<u128, 32> fpsimd_regs, u64& sp, const u64& pc)
|
||||||
|
: m_memory(memory), m_regs(regs), m_fpsimd_regs(fpsimd_regs), m_sp(sp), m_pc(pc) {}
|
||||||
|
~InterpreterVisitor() override = default;
|
||||||
|
|
||||||
|
enum class MemOp {
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
Prefetch,
|
||||||
|
};
|
||||||
|
|
||||||
|
u128 GetVec(Vec v);
|
||||||
|
u64 GetReg(Reg r);
|
||||||
|
u64 GetSp();
|
||||||
|
u64 GetPc();
|
||||||
|
|
||||||
|
void SetVec(Vec v, u128 value);
|
||||||
|
void SetReg(Reg r, u64 value);
|
||||||
|
void SetSp(u64 value);
|
||||||
|
|
||||||
|
u64 ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift);
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store Exclusive
|
||||||
|
bool Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt);
|
||||||
|
bool STLLR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
bool STLR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
bool LDLAR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
bool LDAR(Imm<2> size, Reg Rn, Reg Rt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load register (literal)
|
||||||
|
bool LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) override;
|
||||||
|
bool LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store register pair
|
||||||
|
bool STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Reg Rt2,
|
||||||
|
Reg Rn, Reg Rt) override;
|
||||||
|
bool STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Vec Vt2,
|
||||||
|
Reg Rn, Vec Vt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store register (immediate)
|
||||||
|
bool RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset, Imm<2> size,
|
||||||
|
Imm<2> opc, Reg Rn, Reg Rt);
|
||||||
|
bool STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||||
|
Reg Rt) override;
|
||||||
|
bool STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) override;
|
||||||
|
bool STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) override;
|
||||||
|
|
||||||
|
bool SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset, MemOp memop, Reg Rn,
|
||||||
|
Vec Vt);
|
||||||
|
bool STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
bool STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override;
|
||||||
|
bool LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
bool LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override;
|
||||||
|
bool STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override;
|
||||||
|
bool LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override;
|
||||||
|
|
||||||
|
// Loads and stores - Load/Store register (register offset)
|
||||||
|
bool RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1, Imm<1> opc_0, Reg Rm,
|
||||||
|
Imm<3> option, Reg Rn, Reg Rt);
|
||||||
|
bool STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) override;
|
||||||
|
bool LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Reg Rt) override;
|
||||||
|
|
||||||
|
bool SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Vec Vt);
|
||||||
|
bool STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
bool LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn,
|
||||||
|
Vec Vt) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::Memory::Memory& m_memory;
|
||||||
|
std::span<u64, 31> m_regs;
|
||||||
|
std::span<u128, 32> m_fpsimd_regs;
|
||||||
|
u64& m_sp;
|
||||||
|
const u64& m_pc;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||||
|
fpsimd_context* fpsimd_context);
|
||||||
|
|
||||||
|
} // namespace Core
|
2783
src/core/arm/nce/visitor_base.h
Normal file
2783
src/core/arm/nce/visitor_base.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,6 @@
|
|||||||
#include "core/file_sys/savedata_factory.h"
|
#include "core/file_sys/savedata_factory.h"
|
||||||
#include "core/file_sys/vfs_concat.h"
|
#include "core/file_sys/vfs_concat.h"
|
||||||
#include "core/file_sys/vfs_real.h"
|
#include "core/file_sys/vfs_real.h"
|
||||||
#include "core/gpu_dirty_memory_manager.h"
|
|
||||||
#include "core/hid/hid_core.h"
|
#include "core/hid/hid_core.h"
|
||||||
#include "core/hle/kernel/k_memory_manager.h"
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
@ -36,6 +35,7 @@
|
|||||||
#include "core/hle/kernel/k_scheduler.h"
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/physical_core.h"
|
#include "core/hle/kernel/physical_core.h"
|
||||||
|
#include "core/hle/service/acc/profile_manager.h"
|
||||||
#include "core/hle/service/am/applets/applets.h"
|
#include "core/hle/service/am/applets/applets.h"
|
||||||
#include "core/hle/service/apm/apm_controller.h"
|
#include "core/hle/service/apm/apm_controller.h"
|
||||||
#include "core/hle/service/filesystem/filesystem.h"
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
@ -129,11 +129,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
|||||||
|
|
||||||
struct System::Impl {
|
struct System::Impl {
|
||||||
explicit Impl(System& system)
|
explicit Impl(System& system)
|
||||||
: kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
|
: kernel{system}, fs_controller{system}, hid_core{}, room_network{}, cpu_manager{system},
|
||||||
cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system},
|
reporter{system}, applet_manager{system}, profile_manager{}, time_manager{system} {}
|
||||||
gpu_dirty_memory_write_manager{} {
|
|
||||||
memory.SetGPUDirtyManagers(gpu_dirty_memory_write_manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Initialize(System& system) {
|
void Initialize(System& system) {
|
||||||
device_memory = std::make_unique<Core::DeviceMemory>();
|
device_memory = std::make_unique<Core::DeviceMemory>();
|
||||||
@ -240,17 +237,17 @@ struct System::Impl {
|
|||||||
debugger = std::make_unique<Debugger>(system, port);
|
debugger = std::make_unique<Debugger>(system, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
void InitializeKernel(System& system) {
|
||||||
LOG_DEBUG(Core, "initialized OK");
|
LOG_DEBUG(Core, "initialized OK");
|
||||||
|
|
||||||
// Setting changes may require a full system reinitialization (e.g., disabling multicore).
|
// Setting changes may require a full system reinitialization (e.g., disabling multicore).
|
||||||
ReinitializeIfNecessary(system);
|
ReinitializeIfNecessary(system);
|
||||||
|
|
||||||
memory.SetGPUDirtyManagers(gpu_dirty_memory_write_manager);
|
|
||||||
|
|
||||||
kernel.Initialize();
|
kernel.Initialize();
|
||||||
cpu_manager.Initialize();
|
cpu_manager.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
||||||
/// Reset all glue registrations
|
/// Reset all glue registrations
|
||||||
arp_manager.ResetAll();
|
arp_manager.ResetAll();
|
||||||
|
|
||||||
@ -299,17 +296,9 @@ struct System::Impl {
|
|||||||
return SystemResultStatus::ErrorGetLoader;
|
return SystemResultStatus::ErrorGetLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
|
InitializeKernel(system);
|
||||||
if (init_result != SystemResultStatus::Success) {
|
|
||||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
|
||||||
static_cast<int>(init_result));
|
|
||||||
ShutdownMainProcess();
|
|
||||||
return init_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
|
// Create the application process.
|
||||||
|
|
||||||
// Create the process.
|
|
||||||
auto main_process = Kernel::KProcess::Create(system.Kernel());
|
auto main_process = Kernel::KProcess::Create(system.Kernel());
|
||||||
Kernel::KProcess::Register(system.Kernel(), main_process);
|
Kernel::KProcess::Register(system.Kernel(), main_process);
|
||||||
kernel.AppendNewProcess(main_process);
|
kernel.AppendNewProcess(main_process);
|
||||||
@ -322,7 +311,18 @@ struct System::Impl {
|
|||||||
return static_cast<SystemResultStatus>(
|
return static_cast<SystemResultStatus>(
|
||||||
static_cast<u32>(SystemResultStatus::ErrorLoader) + static_cast<u32>(load_result));
|
static_cast<u32>(SystemResultStatus::ErrorLoader) + static_cast<u32>(load_result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the rest of the system.
|
||||||
|
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
|
||||||
|
if (init_result != SystemResultStatus::Success) {
|
||||||
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||||
|
static_cast<int>(init_result));
|
||||||
|
ShutdownMainProcess();
|
||||||
|
return init_result;
|
||||||
|
}
|
||||||
|
|
||||||
AddGlueRegistrationForProcess(*app_loader, *main_process);
|
AddGlueRegistrationForProcess(*app_loader, *main_process);
|
||||||
|
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
|
||||||
|
|
||||||
// Initialize cheat engine
|
// Initialize cheat engine
|
||||||
if (cheat_engine) {
|
if (cheat_engine) {
|
||||||
@ -425,7 +425,6 @@ struct System::Impl {
|
|||||||
cpu_manager.Shutdown();
|
cpu_manager.Shutdown();
|
||||||
debugger.reset();
|
debugger.reset();
|
||||||
kernel.Shutdown();
|
kernel.Shutdown();
|
||||||
memory.Reset();
|
|
||||||
Network::RestartSocketOperations();
|
Network::RestartSocketOperations();
|
||||||
|
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||||
@ -506,7 +505,6 @@ struct System::Impl {
|
|||||||
std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
|
std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
|
||||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||||
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
||||||
Core::Memory::Memory memory;
|
|
||||||
Core::HID::HIDCore hid_core;
|
Core::HID::HIDCore hid_core;
|
||||||
Network::RoomNetwork room_network;
|
Network::RoomNetwork room_network;
|
||||||
|
|
||||||
@ -532,6 +530,7 @@ struct System::Impl {
|
|||||||
|
|
||||||
/// Service State
|
/// Service State
|
||||||
Service::Glue::ARPManager arp_manager;
|
Service::Glue::ARPManager arp_manager;
|
||||||
|
Service::Account::ProfileManager profile_manager;
|
||||||
Service::Time::TimeManager time_manager;
|
Service::Time::TimeManager time_manager;
|
||||||
|
|
||||||
/// Service manager
|
/// Service manager
|
||||||
@ -565,9 +564,6 @@ struct System::Impl {
|
|||||||
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
|
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
|
||||||
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
|
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
|
||||||
|
|
||||||
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
|
|
||||||
gpu_dirty_memory_write_manager{};
|
|
||||||
|
|
||||||
std::deque<std::vector<u8>> user_channel;
|
std::deque<std::vector<u8>> user_channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -650,29 +646,12 @@ void System::PrepareReschedule(const u32 core_index) {
|
|||||||
impl->kernel.PrepareReschedule(core_index);
|
impl->kernel.PrepareReschedule(core_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::GPUDirtyMemoryManager& System::CurrentGPUDirtyMemoryManager() {
|
|
||||||
const std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
|
||||||
return impl->gpu_dirty_memory_write_manager[core < Core::Hardware::NUM_CPU_CORES
|
|
||||||
? core
|
|
||||||
: Core::Hardware::NUM_CPU_CORES - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides a constant reference to the current gou dirty memory manager.
|
|
||||||
const Core::GPUDirtyMemoryManager& System::CurrentGPUDirtyMemoryManager() const {
|
|
||||||
const std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
|
||||||
return impl->gpu_dirty_memory_write_manager[core < Core::Hardware::NUM_CPU_CORES
|
|
||||||
? core
|
|
||||||
: Core::Hardware::NUM_CPU_CORES - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t System::GetCurrentHostThreadID() const {
|
size_t System::GetCurrentHostThreadID() const {
|
||||||
return impl->kernel.GetCurrentHostThreadID();
|
return impl->kernel.GetCurrentHostThreadID();
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
|
void System::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
|
||||||
for (auto& manager : impl->gpu_dirty_memory_write_manager) {
|
return this->ApplicationProcess()->GatherGPUDirtyMemory(callback);
|
||||||
manager.Gather(callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PerfStatsResults System::GetAndResetPerfStats() {
|
PerfStatsResults System::GetAndResetPerfStats() {
|
||||||
@ -721,20 +700,12 @@ const Kernel::KProcess* System::ApplicationProcess() const {
|
|||||||
return impl->kernel.ApplicationProcess();
|
return impl->kernel.ApplicationProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExclusiveMonitor& System::Monitor() {
|
|
||||||
return impl->kernel.GetExclusiveMonitor();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExclusiveMonitor& System::Monitor() const {
|
|
||||||
return impl->kernel.GetExclusiveMonitor();
|
|
||||||
}
|
|
||||||
|
|
||||||
Memory::Memory& System::ApplicationMemory() {
|
Memory::Memory& System::ApplicationMemory() {
|
||||||
return impl->memory;
|
return impl->kernel.ApplicationProcess()->GetMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Core::Memory::Memory& System::ApplicationMemory() const {
|
const Core::Memory::Memory& System::ApplicationMemory() const {
|
||||||
return impl->memory;
|
return impl->kernel.ApplicationProcess()->GetMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
Tegra::GPU& System::GPU() {
|
Tegra::GPU& System::GPU() {
|
||||||
@ -921,6 +892,14 @@ const Service::APM::Controller& System::GetAPMController() const {
|
|||||||
return impl->apm_controller;
|
return impl->apm_controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Service::Account::ProfileManager& System::GetProfileManager() {
|
||||||
|
return impl->profile_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Service::Account::ProfileManager& System::GetProfileManager() const {
|
||||||
|
return impl->profile_manager;
|
||||||
|
}
|
||||||
|
|
||||||
Service::Time::TimeManager& System::GetTimeManager() {
|
Service::Time::TimeManager& System::GetTimeManager() {
|
||||||
return impl->time_manager;
|
return impl->time_manager;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,10 @@ class Memory;
|
|||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
|
|
||||||
|
namespace Account {
|
||||||
|
class ProfileManager;
|
||||||
|
} // namespace Account
|
||||||
|
|
||||||
namespace AM::Applets {
|
namespace AM::Applets {
|
||||||
struct AppletFrontendSet;
|
struct AppletFrontendSet;
|
||||||
class AppletManager;
|
class AppletManager;
|
||||||
@ -112,7 +116,6 @@ class CpuManager;
|
|||||||
class Debugger;
|
class Debugger;
|
||||||
class DeviceMemory;
|
class DeviceMemory;
|
||||||
class ExclusiveMonitor;
|
class ExclusiveMonitor;
|
||||||
class GPUDirtyMemoryManager;
|
|
||||||
class PerfStats;
|
class PerfStats;
|
||||||
class Reporter;
|
class Reporter;
|
||||||
class SpeedLimiter;
|
class SpeedLimiter;
|
||||||
@ -221,12 +224,6 @@ public:
|
|||||||
/// Prepare the core emulation for a reschedule
|
/// Prepare the core emulation for a reschedule
|
||||||
void PrepareReschedule(u32 core_index);
|
void PrepareReschedule(u32 core_index);
|
||||||
|
|
||||||
/// Provides a reference to the gou dirty memory manager.
|
|
||||||
[[nodiscard]] Core::GPUDirtyMemoryManager& CurrentGPUDirtyMemoryManager();
|
|
||||||
|
|
||||||
/// Provides a constant reference to the current gou dirty memory manager.
|
|
||||||
[[nodiscard]] const Core::GPUDirtyMemoryManager& CurrentGPUDirtyMemoryManager() const;
|
|
||||||
|
|
||||||
void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
|
void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
|
||||||
|
|
||||||
[[nodiscard]] size_t GetCurrentHostThreadID() const;
|
[[nodiscard]] size_t GetCurrentHostThreadID() const;
|
||||||
@ -246,12 +243,6 @@ public:
|
|||||||
/// Gets a const reference to the underlying CPU manager
|
/// Gets a const reference to the underlying CPU manager
|
||||||
[[nodiscard]] const CpuManager& GetCpuManager() const;
|
[[nodiscard]] const CpuManager& GetCpuManager() const;
|
||||||
|
|
||||||
/// Gets a reference to the exclusive monitor
|
|
||||||
[[nodiscard]] ExclusiveMonitor& Monitor();
|
|
||||||
|
|
||||||
/// Gets a constant reference to the exclusive monitor
|
|
||||||
[[nodiscard]] const ExclusiveMonitor& Monitor() const;
|
|
||||||
|
|
||||||
/// Gets a mutable reference to the system memory instance.
|
/// Gets a mutable reference to the system memory instance.
|
||||||
[[nodiscard]] Core::Memory::Memory& ApplicationMemory();
|
[[nodiscard]] Core::Memory::Memory& ApplicationMemory();
|
||||||
|
|
||||||
@ -383,6 +374,9 @@ public:
|
|||||||
[[nodiscard]] Service::APM::Controller& GetAPMController();
|
[[nodiscard]] Service::APM::Controller& GetAPMController();
|
||||||
[[nodiscard]] const Service::APM::Controller& GetAPMController() const;
|
[[nodiscard]] const Service::APM::Controller& GetAPMController() const;
|
||||||
|
|
||||||
|
[[nodiscard]] Service::Account::ProfileManager& GetProfileManager();
|
||||||
|
[[nodiscard]] const Service::Account::ProfileManager& GetProfileManager() const;
|
||||||
|
|
||||||
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
|
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
|
||||||
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
|
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
|
|||||||
struct CoreTiming::Event {
|
struct CoreTiming::Event {
|
||||||
s64 time;
|
s64 time;
|
||||||
u64 fifo_order;
|
u64 fifo_order;
|
||||||
std::uintptr_t user_data;
|
|
||||||
std::weak_ptr<EventType> type;
|
std::weak_ptr<EventType> type;
|
||||||
s64 reschedule_time;
|
s64 reschedule_time;
|
||||||
heap_t::handle_type handle{};
|
heap_t::handle_type handle{};
|
||||||
@ -67,17 +66,15 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
|||||||
event_fifo_id = 0;
|
event_fifo_id = 0;
|
||||||
shutting_down = false;
|
shutting_down = false;
|
||||||
cpu_ticks = 0;
|
cpu_ticks = 0;
|
||||||
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
|
|
||||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
|
||||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
|
||||||
if (is_multicore) {
|
if (is_multicore) {
|
||||||
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
|
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreTiming::ClearPendingEvents() {
|
void CoreTiming::ClearPendingEvents() {
|
||||||
std::scoped_lock lock{basic_lock};
|
std::scoped_lock lock{advance_lock, basic_lock};
|
||||||
event_queue.clear();
|
event_queue.clear();
|
||||||
|
event.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreTiming::Pause(bool is_paused) {
|
void CoreTiming::Pause(bool is_paused) {
|
||||||
@ -119,14 +116,12 @@ bool CoreTiming::HasPendingEvents() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||||
const std::shared_ptr<EventType>& event_type,
|
const std::shared_ptr<EventType>& event_type, bool absolute_time) {
|
||||||
std::uintptr_t user_data, bool absolute_time) {
|
|
||||||
{
|
{
|
||||||
std::scoped_lock scope{basic_lock};
|
std::scoped_lock scope{basic_lock};
|
||||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||||
|
|
||||||
auto h{event_queue.emplace(
|
auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, event_type, 0})};
|
||||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, 0})};
|
|
||||||
(*h).handle = h;
|
(*h).handle = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,13 +131,13 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
|||||||
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||||
std::chrono::nanoseconds resched_time,
|
std::chrono::nanoseconds resched_time,
|
||||||
const std::shared_ptr<EventType>& event_type,
|
const std::shared_ptr<EventType>& event_type,
|
||||||
std::uintptr_t user_data, bool absolute_time) {
|
bool absolute_time) {
|
||||||
{
|
{
|
||||||
std::scoped_lock scope{basic_lock};
|
std::scoped_lock scope{basic_lock};
|
||||||
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
|
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
|
||||||
|
|
||||||
auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, user_data, event_type,
|
auto h{event_queue.emplace(
|
||||||
resched_time.count()})};
|
Event{next_time.count(), event_fifo_id++, event_type, resched_time.count()})};
|
||||||
(*h).handle = h;
|
(*h).handle = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,14 +145,14 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||||
std::uintptr_t user_data, bool wait) {
|
UnscheduleEventType type) {
|
||||||
{
|
{
|
||||||
std::scoped_lock lk{basic_lock};
|
std::scoped_lock lk{basic_lock};
|
||||||
|
|
||||||
std::vector<heap_t::handle_type> to_remove;
|
std::vector<heap_t::handle_type> to_remove;
|
||||||
for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
|
for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
|
||||||
const Event& e = *itr;
|
const Event& e = *itr;
|
||||||
if (e.type.lock().get() == event_type.get() && e.user_data == user_data) {
|
if (e.type.lock().get() == event_type.get()) {
|
||||||
to_remove.push_back(itr->handle);
|
to_remove.push_back(itr->handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,10 +160,12 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
|||||||
for (auto h : to_remove) {
|
for (auto h : to_remove) {
|
||||||
event_queue.erase(h);
|
event_queue.erase(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event_type->sequence_number++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force any in-progress events to finish
|
// Force any in-progress events to finish
|
||||||
if (wait) {
|
if (type == UnscheduleEventType::Wait) {
|
||||||
std::scoped_lock lk{advance_lock};
|
std::scoped_lock lk{advance_lock};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,28 +205,31 @@ std::optional<s64> CoreTiming::Advance() {
|
|||||||
const Event& evt = event_queue.top();
|
const Event& evt = event_queue.top();
|
||||||
|
|
||||||
if (const auto event_type{evt.type.lock()}) {
|
if (const auto event_type{evt.type.lock()}) {
|
||||||
if (evt.reschedule_time == 0) {
|
|
||||||
const auto evt_user_data = evt.user_data;
|
|
||||||
const auto evt_time = evt.time;
|
const auto evt_time = evt.time;
|
||||||
|
const auto evt_sequence_num = event_type->sequence_number;
|
||||||
|
|
||||||
|
if (evt.reschedule_time == 0) {
|
||||||
event_queue.pop();
|
event_queue.pop();
|
||||||
|
|
||||||
basic_lock.unlock();
|
basic_lock.unlock();
|
||||||
|
|
||||||
event_type->callback(
|
event_type->callback(
|
||||||
evt_user_data, evt_time,
|
evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
|
||||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
|
|
||||||
|
|
||||||
basic_lock.lock();
|
basic_lock.lock();
|
||||||
} else {
|
} else {
|
||||||
basic_lock.unlock();
|
basic_lock.unlock();
|
||||||
|
|
||||||
const auto new_schedule_time{event_type->callback(
|
const auto new_schedule_time{event_type->callback(
|
||||||
evt.user_data, evt.time,
|
evt_time, std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time})};
|
||||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
|
|
||||||
|
|
||||||
basic_lock.lock();
|
basic_lock.lock();
|
||||||
|
|
||||||
|
if (evt_sequence_num != event_type->sequence_number) {
|
||||||
|
// Heap handle is invalidated after external modification.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const auto next_schedule_time{new_schedule_time.has_value()
|
const auto next_schedule_time{new_schedule_time.has_value()
|
||||||
? new_schedule_time.value().count()
|
? new_schedule_time.value().count()
|
||||||
: evt.reschedule_time};
|
: evt.reschedule_time};
|
||||||
@ -241,8 +241,8 @@ std::optional<s64> CoreTiming::Advance() {
|
|||||||
next_time = pause_end_time + next_schedule_time;
|
next_time = pause_end_time + next_schedule_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.user_data,
|
event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.type,
|
||||||
evt.type, next_schedule_time, evt.handle});
|
next_schedule_time, evt.handle});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,17 +22,25 @@ namespace Core::Timing {
|
|||||||
|
|
||||||
/// A callback that may be scheduled for a particular core timing event.
|
/// A callback that may be scheduled for a particular core timing event.
|
||||||
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
|
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
|
||||||
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
|
s64 time, std::chrono::nanoseconds ns_late)>;
|
||||||
|
|
||||||
/// Contains the characteristics of a particular event.
|
/// Contains the characteristics of a particular event.
|
||||||
struct EventType {
|
struct EventType {
|
||||||
explicit EventType(TimedCallback&& callback_, std::string&& name_)
|
explicit EventType(TimedCallback&& callback_, std::string&& name_)
|
||||||
: callback{std::move(callback_)}, name{std::move(name_)} {}
|
: callback{std::move(callback_)}, name{std::move(name_)}, sequence_number{0} {}
|
||||||
|
|
||||||
/// The event's callback function.
|
/// The event's callback function.
|
||||||
TimedCallback callback;
|
TimedCallback callback;
|
||||||
/// A pointer to the name of the event.
|
/// A pointer to the name of the event.
|
||||||
const std::string name;
|
const std::string name;
|
||||||
|
/// A monotonic sequence number, incremented when this event is
|
||||||
|
/// changed externally.
|
||||||
|
size_t sequence_number;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class UnscheduleEventType {
|
||||||
|
Wait,
|
||||||
|
NoWait,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,23 +97,17 @@ public:
|
|||||||
|
|
||||||
/// Schedules an event in core timing
|
/// Schedules an event in core timing
|
||||||
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||||
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0,
|
const std::shared_ptr<EventType>& event_type, bool absolute_time = false);
|
||||||
bool absolute_time = false);
|
|
||||||
|
|
||||||
/// Schedules an event which will automatically re-schedule itself with the given time, until
|
/// Schedules an event which will automatically re-schedule itself with the given time, until
|
||||||
/// unscheduled
|
/// unscheduled
|
||||||
void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||||
std::chrono::nanoseconds resched_time,
|
std::chrono::nanoseconds resched_time,
|
||||||
const std::shared_ptr<EventType>& event_type,
|
const std::shared_ptr<EventType>& event_type,
|
||||||
std::uintptr_t user_data = 0, bool absolute_time = false);
|
bool absolute_time = false);
|
||||||
|
|
||||||
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data,
|
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||||
bool wait = true);
|
UnscheduleEventType type = UnscheduleEventType::Wait);
|
||||||
|
|
||||||
void UnscheduleEventWithoutWait(const std::shared_ptr<EventType>& event_type,
|
|
||||||
std::uintptr_t user_data) {
|
|
||||||
UnscheduleEvent(event_type, user_data, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddTicks(u64 ticks_to_add);
|
void AddTicks(u64 ticks_to_add);
|
||||||
|
|
||||||
@ -158,7 +160,6 @@ private:
|
|||||||
heap_t event_queue;
|
heap_t event_queue;
|
||||||
u64 event_fifo_id = 0;
|
u64 event_fifo_id = 0;
|
||||||
|
|
||||||
std::shared_ptr<EventType> ev_lost;
|
|
||||||
Common::Event event{};
|
Common::Event event{};
|
||||||
Common::Event pause_event{};
|
Common::Event pause_event{};
|
||||||
mutable std::mutex basic_lock;
|
mutable std::mutex basic_lock;
|
||||||
|
@ -166,6 +166,10 @@ u32 ProgramMetadata::GetSystemResourceSize() const {
|
|||||||
return npdm_header.system_resource_size;
|
return npdm_header.system_resource_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PoolPartition ProgramMetadata::GetPoolPartition() const {
|
||||||
|
return acid_header.pool_partition;
|
||||||
|
}
|
||||||
|
|
||||||
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
|
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
|
||||||
return aci_kernel_capabilities;
|
return aci_kernel_capabilities;
|
||||||
}
|
}
|
||||||
@ -201,7 +205,7 @@ void ProgramMetadata::Print() const {
|
|||||||
// Begin ACID printing (potential perms, signed)
|
// Begin ACID printing (potential perms, signed)
|
||||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data());
|
LOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data());
|
||||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags);
|
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags);
|
||||||
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO");
|
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.production_flag ? "YES" : "NO");
|
||||||
LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
|
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);
|
LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
|
||||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
||||||
|
@ -34,6 +34,13 @@ enum class ProgramFilePermission : u64 {
|
|||||||
Everything = 1ULL << 63,
|
Everything = 1ULL << 63,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class PoolPartition : u32 {
|
||||||
|
Application = 0,
|
||||||
|
Applet = 1,
|
||||||
|
System = 2,
|
||||||
|
SystemNonSecure = 3,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper which implements an interface to parse Program Description Metadata (NPDM)
|
* Helper which implements an interface to parse Program Description Metadata (NPDM)
|
||||||
* Data can either be loaded from a file path or with data and an offset into it.
|
* Data can either be loaded from a file path or with data and an offset into it.
|
||||||
@ -72,6 +79,7 @@ public:
|
|||||||
u64 GetTitleID() const;
|
u64 GetTitleID() const;
|
||||||
u64 GetFilesystemPermissions() const;
|
u64 GetFilesystemPermissions() const;
|
||||||
u32 GetSystemResourceSize() const;
|
u32 GetSystemResourceSize() const;
|
||||||
|
PoolPartition GetPoolPartition() const;
|
||||||
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
|
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
|
||||||
const std::array<u8, 0x10>& GetName() const {
|
const std::array<u8, 0x10>& GetName() const {
|
||||||
return npdm_header.application_name;
|
return npdm_header.application_name;
|
||||||
@ -116,8 +124,9 @@ private:
|
|||||||
union {
|
union {
|
||||||
u32 flags;
|
u32 flags;
|
||||||
|
|
||||||
BitField<0, 1, u32> is_retail;
|
BitField<0, 1, u32> production_flag;
|
||||||
BitField<1, 31, u32> flags_unk;
|
BitField<1, 1, u32> unqualified_approval;
|
||||||
|
BitField<2, 4, PoolPartition> pool_partition;
|
||||||
};
|
};
|
||||||
u64_le title_id_min;
|
u64_le title_id_min;
|
||||||
u64_le title_id_max;
|
u64_le title_id_max;
|
||||||
|
@ -201,8 +201,6 @@ std::string VfsFile::GetFullPath() const {
|
|||||||
|
|
||||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||||
auto vec = Common::FS::SplitPathComponents(path);
|
auto vec = Common::FS::SplitPathComponents(path);
|
||||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
|
||||||
vec.end());
|
|
||||||
if (vec.empty()) {
|
if (vec.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -237,8 +235,6 @@ VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
|
|||||||
|
|
||||||
VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||||
auto vec = Common::FS::SplitPathComponents(path);
|
auto vec = Common::FS::SplitPathComponents(path);
|
||||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
|
||||||
vec.end());
|
|
||||||
if (vec.empty()) {
|
if (vec.empty()) {
|
||||||
// TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
|
// TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
|
||||||
// because of const-ness
|
// because of const-ness
|
||||||
@ -303,8 +299,6 @@ std::size_t VfsDirectory::GetSize() const {
|
|||||||
|
|
||||||
VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
|
VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
|
||||||
auto vec = Common::FS::SplitPathComponents(path);
|
auto vec = Common::FS::SplitPathComponents(path);
|
||||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
|
||||||
vec.end());
|
|
||||||
if (vec.empty()) {
|
if (vec.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -334,8 +328,6 @@ VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
|
|||||||
|
|
||||||
VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||||
auto vec = Common::FS::SplitPathComponents(path);
|
auto vec = Common::FS::SplitPathComponents(path);
|
||||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
|
||||||
vec.end());
|
|
||||||
if (vec.empty()) {
|
if (vec.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -268,7 +268,7 @@ void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference)
|
|||||||
RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
|
RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
|
||||||
const std::string& path_, Mode perms_, std::optional<u64> size_)
|
const std::string& path_, Mode perms_, std::optional<u64> size_)
|
||||||
: base(base_), reference(std::move(reference_)), path(path_),
|
: base(base_), reference(std::move(reference_)), path(path_),
|
||||||
parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)),
|
parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponentsCopy(path_)),
|
||||||
size(size_), perms(perms_) {}
|
size(size_), perms(perms_) {}
|
||||||
|
|
||||||
RealVfsFile::~RealVfsFile() {
|
RealVfsFile::~RealVfsFile() {
|
||||||
@ -276,7 +276,7 @@ RealVfsFile::~RealVfsFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string RealVfsFile::GetName() const {
|
std::string RealVfsFile::GetName() const {
|
||||||
return path_components.back();
|
return path_components.empty() ? "" : std::string(path_components.back());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t RealVfsFile::GetSize() const {
|
std::size_t RealVfsFile::GetSize() const {
|
||||||
@ -375,7 +375,7 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
|
|||||||
|
|
||||||
RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
|
RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
|
||||||
: base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
|
: base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
|
||||||
path_components(FS::SplitPathComponents(path)), perms(perms_) {
|
path_components(FS::SplitPathComponentsCopy(path)), perms(perms_) {
|
||||||
if (!FS::Exists(path) && True(perms & Mode::Write)) {
|
if (!FS::Exists(path) && True(perms & Mode::Write)) {
|
||||||
void(FS::CreateDirs(path));
|
void(FS::CreateDirs(path));
|
||||||
}
|
}
|
||||||
@ -464,7 +464,7 @@ bool RealVfsDirectory::IsReadable() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string RealVfsDirectory::GetName() const {
|
std::string RealVfsDirectory::GetName() const {
|
||||||
return path_components.back();
|
return path_components.empty() ? "" : std::string(path_components.back());
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualDir RealVfsDirectory::GetParentDirectory() const {
|
VirtualDir RealVfsDirectory::GetParentDirectory() const {
|
||||||
|
@ -20,6 +20,9 @@ InputInterpreter::InputInterpreter(Core::System& system)
|
|||||||
InputInterpreter::~InputInterpreter() = default;
|
InputInterpreter::~InputInterpreter() = default;
|
||||||
|
|
||||||
void InputInterpreter::PollInput() {
|
void InputInterpreter::PollInput() {
|
||||||
|
if (npad == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto button_state = npad->GetAndResetPressState();
|
const auto button_state = npad->GetAndResetPressState();
|
||||||
|
|
||||||
previous_index = current_index;
|
previous_index = current_index;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "core/arm/exclusive_monitor.h"
|
#include "core/arm/exclusive_monitor.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/k_address_arbiter.h"
|
#include "core/hle/kernel/k_address_arbiter.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_scheduler.h"
|
#include "core/hle/kernel/k_scheduler.h"
|
||||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
@ -26,9 +27,9 @@ bool ReadFromUser(KernelCore& kernel, s32* out, KProcessAddress address) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DecrementIfLessThan(Core::System& system, s32* out, KProcessAddress address, s32 value) {
|
bool DecrementIfLessThan(KernelCore& kernel, s32* out, KProcessAddress address, s32 value) {
|
||||||
auto& monitor = system.Monitor();
|
auto& monitor = GetCurrentProcess(kernel).GetExclusiveMonitor();
|
||||||
const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
|
const auto current_core = kernel.CurrentPhysicalCoreIndex();
|
||||||
|
|
||||||
// NOTE: If scheduler lock is not held here, interrupt disable is required.
|
// NOTE: If scheduler lock is not held here, interrupt disable is required.
|
||||||
// KScopedInterruptDisable di;
|
// KScopedInterruptDisable di;
|
||||||
@ -66,10 +67,10 @@ bool DecrementIfLessThan(Core::System& system, s32* out, KProcessAddress address
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UpdateIfEqual(Core::System& system, s32* out, KProcessAddress address, s32 value,
|
bool UpdateIfEqual(KernelCore& kernel, s32* out, KProcessAddress address, s32 value,
|
||||||
s32 new_value) {
|
s32 new_value) {
|
||||||
auto& monitor = system.Monitor();
|
auto& monitor = GetCurrentProcess(kernel).GetExclusiveMonitor();
|
||||||
const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
|
const auto current_core = kernel.CurrentPhysicalCoreIndex();
|
||||||
|
|
||||||
// NOTE: If scheduler lock is not held here, interrupt disable is required.
|
// NOTE: If scheduler lock is not held here, interrupt disable is required.
|
||||||
// KScopedInterruptDisable di;
|
// KScopedInterruptDisable di;
|
||||||
@ -159,7 +160,7 @@ Result KAddressArbiter::SignalAndIncrementIfEqual(uint64_t addr, s32 value, s32
|
|||||||
|
|
||||||
// Check the userspace value.
|
// Check the userspace value.
|
||||||
s32 user_value{};
|
s32 user_value{};
|
||||||
R_UNLESS(UpdateIfEqual(m_system, std::addressof(user_value), addr, value, value + 1),
|
R_UNLESS(UpdateIfEqual(m_kernel, std::addressof(user_value), addr, value, value + 1),
|
||||||
ResultInvalidCurrentMemory);
|
ResultInvalidCurrentMemory);
|
||||||
R_UNLESS(user_value == value, ResultInvalidState);
|
R_UNLESS(user_value == value, ResultInvalidState);
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uint64_t addr, s32
|
|||||||
s32 user_value{};
|
s32 user_value{};
|
||||||
bool succeeded{};
|
bool succeeded{};
|
||||||
if (value != new_value) {
|
if (value != new_value) {
|
||||||
succeeded = UpdateIfEqual(m_system, std::addressof(user_value), addr, value, new_value);
|
succeeded = UpdateIfEqual(m_kernel, std::addressof(user_value), addr, value, new_value);
|
||||||
} else {
|
} else {
|
||||||
succeeded = ReadFromUser(m_kernel, std::addressof(user_value), addr);
|
succeeded = ReadFromUser(m_kernel, std::addressof(user_value), addr);
|
||||||
}
|
}
|
||||||
@ -262,7 +263,7 @@ Result KAddressArbiter::WaitIfLessThan(uint64_t addr, s32 value, bool decrement,
|
|||||||
s32 user_value{};
|
s32 user_value{};
|
||||||
bool succeeded{};
|
bool succeeded{};
|
||||||
if (decrement) {
|
if (decrement) {
|
||||||
succeeded = DecrementIfLessThan(m_system, std::addressof(user_value), addr, value);
|
succeeded = DecrementIfLessThan(m_kernel, std::addressof(user_value), addr, value);
|
||||||
} else {
|
} else {
|
||||||
succeeded = ReadFromUser(m_kernel, std::addressof(user_value), addr);
|
succeeded = ReadFromUser(m_kernel, std::addressof(user_value), addr);
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,22 @@
|
|||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
void KAutoObjectWithListContainer::Register(KAutoObjectWithList* obj) {
|
void KAutoObjectWithListContainer::Register(KAutoObjectWithList* obj) {
|
||||||
KScopedLightLock lk(m_lock);
|
// KScopedInterruptDisable di;
|
||||||
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
m_object_list.insert_unique(*obj);
|
m_object_list.insert_unique(*obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KAutoObjectWithListContainer::Unregister(KAutoObjectWithList* obj) {
|
void KAutoObjectWithListContainer::Unregister(KAutoObjectWithList* obj) {
|
||||||
KScopedLightLock lk(m_lock);
|
// KScopedInterruptDisable di;
|
||||||
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
m_object_list.erase(*obj);
|
m_object_list.erase(*obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t KAutoObjectWithListContainer::GetOwnedCount(KProcess* owner) {
|
size_t KAutoObjectWithListContainer::GetOwnedCount(KProcess* owner) {
|
||||||
KScopedLightLock lk(m_lock);
|
// KScopedInterruptDisable di;
|
||||||
|
KScopedSpinLock lk(m_lock);
|
||||||
|
|
||||||
return std::count_if(m_object_list.begin(), m_object_list.end(),
|
return std::count_if(m_object_list.begin(), m_object_list.end(),
|
||||||
[&](const auto& obj) { return obj.GetOwner() == owner; });
|
[&](const auto& obj) { return obj.GetOwner() == owner; });
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "core/hle/kernel/k_auto_object.h"
|
#include "core/hle/kernel/k_auto_object.h"
|
||||||
#include "core/hle/kernel/k_light_lock.h"
|
#include "core/hle/kernel/k_spin_lock.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
@ -21,32 +21,7 @@ public:
|
|||||||
|
|
||||||
using ListType = boost::intrusive::rbtree<KAutoObjectWithList>;
|
using ListType = boost::intrusive::rbtree<KAutoObjectWithList>;
|
||||||
|
|
||||||
class ListAccessor : public KScopedLightLock {
|
KAutoObjectWithListContainer(KernelCore& kernel) : m_lock(), m_object_list() {}
|
||||||
public:
|
|
||||||
explicit ListAccessor(KAutoObjectWithListContainer* container)
|
|
||||||
: KScopedLightLock(container->m_lock), m_list(container->m_object_list) {}
|
|
||||||
explicit ListAccessor(KAutoObjectWithListContainer& container)
|
|
||||||
: KScopedLightLock(container.m_lock), m_list(container.m_object_list) {}
|
|
||||||
|
|
||||||
typename ListType::iterator begin() const {
|
|
||||||
return m_list.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
typename ListType::iterator end() const {
|
|
||||||
return m_list.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
typename ListType::iterator find(typename ListType::const_reference ref) const {
|
|
||||||
return m_list.find(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ListType& m_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
friend class ListAccessor;
|
|
||||||
|
|
||||||
KAutoObjectWithListContainer(KernelCore& kernel) : m_lock(kernel), m_object_list() {}
|
|
||||||
|
|
||||||
void Initialize() {}
|
void Initialize() {}
|
||||||
void Finalize() {}
|
void Finalize() {}
|
||||||
@ -56,7 +31,7 @@ public:
|
|||||||
size_t GetOwnedCount(KProcess* owner);
|
size_t GetOwnedCount(KProcess* owner);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KLightLock m_lock;
|
KSpinLock m_lock;
|
||||||
ListType m_object_list;
|
ListType m_object_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,9 +58,8 @@ Result KClientPort::CreateSession(KClientSession** out) {
|
|||||||
KSession* session{};
|
KSession* session{};
|
||||||
|
|
||||||
// Reserve a new session from the resource limit.
|
// Reserve a new session from the resource limit.
|
||||||
//! FIXME: we are reserving this from the wrong resource limit!
|
KScopedResourceReservation session_reservation(GetCurrentProcessPointer(m_kernel),
|
||||||
KScopedResourceReservation session_reservation(
|
LimitableResource::SessionCountMax);
|
||||||
m_kernel.ApplicationProcess()->GetResourceLimit(), LimitableResource::SessionCountMax);
|
|
||||||
R_UNLESS(session_reservation.Succeeded(), ResultLimitReached);
|
R_UNLESS(session_reservation.Succeeded(), ResultLimitReached);
|
||||||
|
|
||||||
// Allocate a session normally.
|
// Allocate a session normally.
|
||||||
|
@ -28,10 +28,10 @@ bool WriteToUser(KernelCore& kernel, KProcessAddress address, const u32* p) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UpdateLockAtomic(Core::System& system, u32* out, KProcessAddress address, u32 if_zero,
|
bool UpdateLockAtomic(KernelCore& kernel, u32* out, KProcessAddress address, u32 if_zero,
|
||||||
u32 new_orr_mask) {
|
u32 new_orr_mask) {
|
||||||
auto& monitor = system.Monitor();
|
auto& monitor = GetCurrentProcess(kernel).GetExclusiveMonitor();
|
||||||
const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
|
const auto current_core = kernel.CurrentPhysicalCoreIndex();
|
||||||
|
|
||||||
u32 expected{};
|
u32 expected{};
|
||||||
|
|
||||||
@ -208,7 +208,7 @@ void KConditionVariable::SignalImpl(KThread* thread) {
|
|||||||
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
||||||
can_access = true;
|
can_access = true;
|
||||||
if (can_access) [[likely]] {
|
if (can_access) [[likely]] {
|
||||||
UpdateLockAtomic(m_system, std::addressof(prev_tag), address, own_tag,
|
UpdateLockAtomic(m_kernel, std::addressof(prev_tag), address, own_tag,
|
||||||
Svc::HandleWaitMask);
|
Svc::HandleWaitMask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,7 @@ public:
|
|||||||
// Handle pseudo-handles.
|
// Handle pseudo-handles.
|
||||||
if constexpr (std::derived_from<KProcess, T>) {
|
if constexpr (std::derived_from<KProcess, T>) {
|
||||||
if (handle == Svc::PseudoHandle::CurrentProcess) {
|
if (handle == Svc::PseudoHandle::CurrentProcess) {
|
||||||
//! FIXME: this is the wrong process!
|
auto* const cur_process = GetCurrentProcessPointer(m_kernel);
|
||||||
auto* const cur_process = m_kernel.ApplicationProcess();
|
|
||||||
ASSERT(cur_process != nullptr);
|
ASSERT(cur_process != nullptr);
|
||||||
return cur_process;
|
return cur_process;
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,15 @@ namespace Kernel {
|
|||||||
|
|
||||||
void KHardwareTimer::Initialize() {
|
void KHardwareTimer::Initialize() {
|
||||||
// Create the timing callback to register with CoreTiming.
|
// Create the timing callback to register with CoreTiming.
|
||||||
m_event_type = Core::Timing::CreateEvent(
|
m_event_type = Core::Timing::CreateEvent("KHardwareTimer::Callback",
|
||||||
"KHardwareTimer::Callback", [](std::uintptr_t timer_handle, s64, std::chrono::nanoseconds) {
|
[this](s64, std::chrono::nanoseconds) {
|
||||||
reinterpret_cast<KHardwareTimer*>(timer_handle)->DoTask();
|
this->DoTask();
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void KHardwareTimer::Finalize() {
|
void KHardwareTimer::Finalize() {
|
||||||
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
|
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type);
|
||||||
m_wakeup_time = std::numeric_limits<s64>::max();
|
m_wakeup_time = std::numeric_limits<s64>::max();
|
||||||
m_event_type.reset();
|
m_event_type.reset();
|
||||||
}
|
}
|
||||||
@ -57,13 +57,12 @@ void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
|
|||||||
|
|
||||||
m_wakeup_time = wakeup_time;
|
m_wakeup_time = wakeup_time;
|
||||||
m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
|
m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
|
||||||
m_event_type, reinterpret_cast<uintptr_t>(this),
|
m_event_type, true);
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KHardwareTimer::DisableInterrupt() {
|
void KHardwareTimer::DisableInterrupt() {
|
||||||
m_kernel.System().CoreTiming().UnscheduleEventWithoutWait(m_event_type,
|
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type,
|
||||||
reinterpret_cast<uintptr_t>(this));
|
Core::Timing::UnscheduleEventType::NoWait);
|
||||||
m_wakeup_time = std::numeric_limits<s64>::max();
|
m_wakeup_time = std::numeric_limits<s64>::max();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,8 +421,9 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
|
|||||||
} else {
|
} else {
|
||||||
// Set all the allocated memory.
|
// Set all the allocated memory.
|
||||||
for (const auto& block : *out) {
|
for (const auto& block : *out) {
|
||||||
std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
|
m_system.DeviceMemory().buffer.ClearBackingRegion(GetInteger(block.GetAddress()) -
|
||||||
block.GetSize());
|
Core::DramMemoryMap::Base,
|
||||||
|
block.GetSize(), fill_pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,11 @@ void InvalidateInstructionCache(KernelCore& kernel, AddressType addr, u64 size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClearBackingRegion(Core::System& system, KPhysicalAddress addr, u64 size, u32 fill_value) {
|
||||||
|
system.DeviceMemory().buffer.ClearBackingRegion(GetInteger(addr) - Core::DramMemoryMap::Base,
|
||||||
|
size, fill_value);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename AddressType>
|
template <typename AddressType>
|
||||||
Result InvalidateDataCache(AddressType addr, u64 size) {
|
Result InvalidateDataCache(AddressType addr, u64 size) {
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@ -429,7 +434,7 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
|
|||||||
void KPageTableBase::Finalize() {
|
void KPageTableBase::Finalize() {
|
||||||
auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
|
auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
|
||||||
if (Settings::IsFastmemEnabled()) {
|
if (Settings::IsFastmemEnabled()) {
|
||||||
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size);
|
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1363,8 +1368,7 @@ Result KPageTableBase::MapInsecureMemory(KProcessAddress address, size_t size) {
|
|||||||
|
|
||||||
// Clear all the newly allocated pages.
|
// Clear all the newly allocated pages.
|
||||||
for (const auto& it : pg) {
|
for (const auto& it : pg) {
|
||||||
std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()),
|
ClearBackingRegion(m_system, it.GetAddress(), it.GetSize(), m_heap_fill_value);
|
||||||
static_cast<u32>(m_heap_fill_value), it.GetSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the table.
|
// Lock the table.
|
||||||
@ -1570,8 +1574,7 @@ Result KPageTableBase::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProce
|
|||||||
|
|
||||||
// Clear all pages.
|
// Clear all pages.
|
||||||
for (const auto& it : pg) {
|
for (const auto& it : pg) {
|
||||||
std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()),
|
ClearBackingRegion(m_system, it.GetAddress(), it.GetSize(), m_heap_fill_value);
|
||||||
static_cast<u32>(m_heap_fill_value), it.GetSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the pages.
|
// Map the pages.
|
||||||
@ -2159,8 +2162,7 @@ Result KPageTableBase::SetHeapSize(KProcessAddress* out, size_t size) {
|
|||||||
|
|
||||||
// Clear all the newly allocated pages.
|
// Clear all the newly allocated pages.
|
||||||
for (const auto& it : pg) {
|
for (const auto& it : pg) {
|
||||||
std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()), m_heap_fill_value,
|
ClearBackingRegion(m_system, it.GetAddress(), it.GetSize(), m_heap_fill_value);
|
||||||
it.GetSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the pages.
|
// Map the pages.
|
||||||
@ -5241,7 +5243,7 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) {
|
|||||||
// Unmap.
|
// Unmap.
|
||||||
R_ASSERT(this->Operate(updater.GetPageList(), cur_address,
|
R_ASSERT(this->Operate(updater.GetPageList(), cur_address,
|
||||||
cur_pages, 0, false, unmap_properties,
|
cur_pages, 0, false, unmap_properties,
|
||||||
OperationType::Unmap, true));
|
OperationType::UnmapPhysical, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're done.
|
// Check if we're done.
|
||||||
@ -5324,7 +5326,7 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) {
|
|||||||
// Map the papges.
|
// Map the papges.
|
||||||
R_TRY(this->Operate(updater.GetPageList(), cur_address, map_pages,
|
R_TRY(this->Operate(updater.GetPageList(), cur_address, map_pages,
|
||||||
cur_pg, map_properties,
|
cur_pg, map_properties,
|
||||||
OperationType::MapFirstGroup, false));
|
OperationType::MapFirstGroupPhysical, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5478,7 +5480,7 @@ Result KPageTableBase::UnmapPhysicalMemory(KProcessAddress address, size_t size)
|
|||||||
|
|
||||||
// Unmap.
|
// Unmap.
|
||||||
R_ASSERT(this->Operate(updater.GetPageList(), cur_address, cur_pages, 0, false,
|
R_ASSERT(this->Operate(updater.GetPageList(), cur_address, cur_pages, 0, false,
|
||||||
unmap_properties, OperationType::Unmap, false));
|
unmap_properties, OperationType::UnmapPhysical, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're done.
|
// Check if we're done.
|
||||||
@ -5653,7 +5655,10 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
|||||||
// or free them to the page list, and so it goes unused (along with page properties).
|
// or free them to the page list, and so it goes unused (along with page properties).
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case OperationType::Unmap: {
|
case OperationType::Unmap:
|
||||||
|
case OperationType::UnmapPhysical: {
|
||||||
|
const bool separate_heap = operation == OperationType::UnmapPhysical;
|
||||||
|
|
||||||
// Ensure that any pages we track are closed on exit.
|
// Ensure that any pages we track are closed on exit.
|
||||||
KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager());
|
KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager());
|
||||||
SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
|
SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
|
||||||
@ -5662,7 +5667,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
|||||||
this->MakePageGroup(pages_to_close, virt_addr, num_pages);
|
this->MakePageGroup(pages_to_close, virt_addr, num_pages);
|
||||||
|
|
||||||
// Unmap.
|
// Unmap.
|
||||||
m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize);
|
m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize, separate_heap);
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
@ -5670,7 +5675,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
|||||||
ASSERT(virt_addr != 0);
|
ASSERT(virt_addr != 0);
|
||||||
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
|
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
|
||||||
m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
|
m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
|
||||||
ConvertToMemoryPermission(properties.perm));
|
ConvertToMemoryPermission(properties.perm), false);
|
||||||
|
|
||||||
// Open references to pages, if we should.
|
// Open references to pages, if we should.
|
||||||
if (this->IsHeapPhysicalAddress(phys_addr)) {
|
if (this->IsHeapPhysicalAddress(phys_addr)) {
|
||||||
@ -5709,16 +5714,19 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
|
|||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case OperationType::MapGroup:
|
case OperationType::MapGroup:
|
||||||
case OperationType::MapFirstGroup: {
|
case OperationType::MapFirstGroup:
|
||||||
|
case OperationType::MapFirstGroupPhysical: {
|
||||||
|
const bool separate_heap = operation == OperationType::MapFirstGroupPhysical;
|
||||||
|
|
||||||
// We want to maintain a new reference to every page in the group.
|
// We want to maintain a new reference to every page in the group.
|
||||||
KScopedPageGroup spg(page_group, operation != OperationType::MapFirstGroup);
|
KScopedPageGroup spg(page_group, operation == OperationType::MapGroup);
|
||||||
|
|
||||||
for (const auto& node : page_group) {
|
for (const auto& node : page_group) {
|
||||||
const size_t size{node.GetNumPages() * PageSize};
|
const size_t size{node.GetNumPages() * PageSize};
|
||||||
|
|
||||||
// Map the pages.
|
// Map the pages.
|
||||||
m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
|
m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
|
||||||
ConvertToMemoryPermission(properties.perm));
|
ConvertToMemoryPermission(properties.perm), separate_heap);
|
||||||
|
|
||||||
virt_addr += size;
|
virt_addr += size;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,9 @@ protected:
|
|||||||
ChangePermissionsAndRefresh = 5,
|
ChangePermissionsAndRefresh = 5,
|
||||||
ChangePermissionsAndRefreshAndFlush = 6,
|
ChangePermissionsAndRefreshAndFlush = 6,
|
||||||
Separate = 7,
|
Separate = 7,
|
||||||
|
|
||||||
|
MapFirstGroupPhysical = 65000,
|
||||||
|
UnmapPhysical = 65001,
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr size_t MaxPhysicalMapAlignment = 1_GiB;
|
static constexpr size_t MaxPhysicalMapAlignment = 1_GiB;
|
||||||
|
@ -306,12 +306,16 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
|
|||||||
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
|
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
|
||||||
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
|
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
|
||||||
params.code_address, params.code_num_pages * PageSize,
|
params.code_address, params.code_num_pages * PageSize,
|
||||||
m_system_resource, res_limit, this->GetMemory(), 0));
|
m_system_resource, res_limit, m_memory, 0));
|
||||||
}
|
}
|
||||||
ON_RESULT_FAILURE_2 {
|
ON_RESULT_FAILURE_2 {
|
||||||
m_page_table.Finalize();
|
m_page_table.Finalize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure our memory is initialized.
|
||||||
|
m_memory.SetCurrentPageTable(*this);
|
||||||
|
m_memory.SetGPUDirtyManagers(m_dirty_memory_managers);
|
||||||
|
|
||||||
// Ensure we can insert the code region.
|
// Ensure we can insert the code region.
|
||||||
R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize,
|
R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize,
|
||||||
KMemoryState::Code),
|
KMemoryState::Code),
|
||||||
@ -399,12 +403,16 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
|
|||||||
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
|
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
|
||||||
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
|
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
|
||||||
params.code_address, code_size, m_system_resource, res_limit,
|
params.code_address, code_size, m_system_resource, res_limit,
|
||||||
this->GetMemory(), aslr_space_start));
|
m_memory, aslr_space_start));
|
||||||
}
|
}
|
||||||
ON_RESULT_FAILURE_2 {
|
ON_RESULT_FAILURE_2 {
|
||||||
m_page_table.Finalize();
|
m_page_table.Finalize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure our memory is initialized.
|
||||||
|
m_memory.SetCurrentPageTable(*this);
|
||||||
|
m_memory.SetGPUDirtyManagers(m_dirty_memory_managers);
|
||||||
|
|
||||||
// Ensure we can insert the code region.
|
// Ensure we can insert the code region.
|
||||||
R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code),
|
R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code),
|
||||||
ResultInvalidMemoryRegion);
|
ResultInvalidMemoryRegion);
|
||||||
@ -1094,8 +1102,7 @@ void KProcess::UnpinThread(KThread* thread) {
|
|||||||
|
|
||||||
Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids,
|
Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids,
|
||||||
s32 max_out_count) {
|
s32 max_out_count) {
|
||||||
// TODO: use current memory reference
|
auto& memory = this->GetMemory();
|
||||||
auto& memory = m_kernel.System().ApplicationMemory();
|
|
||||||
|
|
||||||
// Lock the list.
|
// Lock the list.
|
||||||
KScopedLightLock lk(m_list_lock);
|
KScopedLightLock lk(m_list_lock);
|
||||||
@ -1128,14 +1135,15 @@ void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {}
|
|||||||
KProcess::KProcess(KernelCore& kernel)
|
KProcess::KProcess(KernelCore& kernel)
|
||||||
: KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel}, m_state_lock{kernel},
|
: KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel}, m_state_lock{kernel},
|
||||||
m_list_lock{kernel}, m_cond_var{kernel.System()}, m_address_arbiter{kernel.System()},
|
m_list_lock{kernel}, m_cond_var{kernel.System()}, m_address_arbiter{kernel.System()},
|
||||||
m_handle_table{kernel} {}
|
m_handle_table{kernel}, m_dirty_memory_managers{},
|
||||||
|
m_exclusive_monitor{}, m_memory{kernel.System()} {}
|
||||||
KProcess::~KProcess() = default;
|
KProcess::~KProcess() = default;
|
||||||
|
|
||||||
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||||
KProcessAddress aslr_space_start, bool is_hbl) {
|
KProcessAddress aslr_space_start, bool is_hbl) {
|
||||||
// Create a resource limit for the process.
|
// Create a resource limit for the process.
|
||||||
const auto physical_memory_size =
|
const auto pool = static_cast<KMemoryManager::Pool>(metadata.GetPoolPartition());
|
||||||
m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
|
const auto physical_memory_size = m_kernel.MemoryManager().GetSize(pool);
|
||||||
auto* res_limit =
|
auto* res_limit =
|
||||||
Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size);
|
Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size);
|
||||||
|
|
||||||
@ -1146,8 +1154,10 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
|
|||||||
Svc::CreateProcessFlag flag{};
|
Svc::CreateProcessFlag flag{};
|
||||||
u64 code_address{};
|
u64 code_address{};
|
||||||
|
|
||||||
// We are an application.
|
// Determine if we are an application.
|
||||||
|
if (pool == KMemoryManager::Pool::Application) {
|
||||||
flag |= Svc::CreateProcessFlag::IsApplication;
|
flag |= Svc::CreateProcessFlag::IsApplication;
|
||||||
|
}
|
||||||
|
|
||||||
// If we are 64-bit, create as such.
|
// If we are 64-bit, create as such.
|
||||||
if (metadata.Is64BitProgram()) {
|
if (metadata.Is64BitProgram()) {
|
||||||
@ -1196,8 +1206,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
|
|||||||
std::memcpy(params.name.data(), name.data(), sizeof(params.name));
|
std::memcpy(params.name.data(), name.data(), sizeof(params.name));
|
||||||
|
|
||||||
// Initialize for application process.
|
// Initialize for application process.
|
||||||
R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit,
|
R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit, pool,
|
||||||
KMemoryManager::Pool::Application, aslr_space_start));
|
aslr_space_start));
|
||||||
|
|
||||||
// Assign remaining properties.
|
// Assign remaining properties.
|
||||||
m_is_hbl = is_hbl;
|
m_is_hbl = is_hbl;
|
||||||
@ -1223,22 +1233,25 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
|
|||||||
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
|
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
|
||||||
|
|
||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
if (Settings::IsNceEnabled()) {
|
if (this->IsApplication() && Settings::IsNceEnabled()) {
|
||||||
auto& buffer = m_kernel.System().DeviceMemory().buffer;
|
auto& buffer = m_kernel.System().DeviceMemory().buffer;
|
||||||
const auto& code = code_set.CodeSegment();
|
const auto& code = code_set.CodeSegment();
|
||||||
const auto& patch = code_set.PatchSegment();
|
const auto& patch = code_set.PatchSegment();
|
||||||
buffer.Protect(GetInteger(base_addr + code.addr), code.size, true, true, true);
|
buffer.Protect(GetInteger(base_addr + code.addr), code.size,
|
||||||
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, true, true, true);
|
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
|
||||||
|
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
|
||||||
|
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
|
||||||
ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
|
ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::InitializeInterfaces() {
|
void KProcess::InitializeInterfaces() {
|
||||||
this->GetMemory().SetCurrentPageTable(*this);
|
m_exclusive_monitor =
|
||||||
|
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
|
||||||
|
|
||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
if (this->Is64Bit() && Settings::IsNceEnabled()) {
|
if (this->IsApplication() && Settings::IsNceEnabled()) {
|
||||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||||
m_arm_interfaces[i] = std::make_unique<Core::ArmNce>(m_kernel.System(), true, i);
|
m_arm_interfaces[i] = std::make_unique<Core::ArmNce>(m_kernel.System(), true, i);
|
||||||
}
|
}
|
||||||
@ -1248,13 +1261,13 @@ void KProcess::InitializeInterfaces() {
|
|||||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||||
m_arm_interfaces[i] = std::make_unique<Core::ArmDynarmic64>(
|
m_arm_interfaces[i] = std::make_unique<Core::ArmDynarmic64>(
|
||||||
m_kernel.System(), m_kernel.IsMulticore(), this,
|
m_kernel.System(), m_kernel.IsMulticore(), this,
|
||||||
static_cast<Core::DynarmicExclusiveMonitor&>(m_kernel.GetExclusiveMonitor()), i);
|
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||||
m_arm_interfaces[i] = std::make_unique<Core::ArmDynarmic32>(
|
m_arm_interfaces[i] = std::make_unique<Core::ArmDynarmic32>(
|
||||||
m_kernel.System(), m_kernel.IsMulticore(), this,
|
m_kernel.System(), m_kernel.IsMulticore(), this,
|
||||||
static_cast<Core::DynarmicExclusiveMonitor&>(m_kernel.GetExclusiveMonitor()), i);
|
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1305,9 +1318,10 @@ bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointT
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::Memory::Memory& KProcess::GetMemory() const {
|
void KProcess::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
|
||||||
// TODO: per-process memory
|
for (auto& manager : m_dirty_memory_managers) {
|
||||||
return m_kernel.System().ApplicationMemory();
|
manager.Gather(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/file_sys/program_metadata.h"
|
#include "core/file_sys/program_metadata.h"
|
||||||
|
#include "core/gpu_dirty_memory_manager.h"
|
||||||
#include "core/hle/kernel/code_set.h"
|
#include "core/hle/kernel/code_set.h"
|
||||||
#include "core/hle/kernel/k_address_arbiter.h"
|
#include "core/hle/kernel/k_address_arbiter.h"
|
||||||
#include "core/hle/kernel/k_capabilities.h"
|
#include "core/hle/kernel/k_capabilities.h"
|
||||||
@ -17,6 +18,7 @@
|
|||||||
#include "core/hle/kernel/k_system_resource.h"
|
#include "core/hle/kernel/k_system_resource.h"
|
||||||
#include "core/hle/kernel/k_thread.h"
|
#include "core/hle/kernel/k_thread.h"
|
||||||
#include "core/hle/kernel/k_thread_local_page.h"
|
#include "core/hle/kernel/k_thread_local_page.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
@ -126,6 +128,9 @@ private:
|
|||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
std::unordered_map<u64, u64> m_post_handlers{};
|
std::unordered_map<u64, u64> m_post_handlers{};
|
||||||
#endif
|
#endif
|
||||||
|
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> m_dirty_memory_managers;
|
||||||
|
std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor;
|
||||||
|
Core::Memory::Memory m_memory;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Result StartTermination();
|
Result StartTermination();
|
||||||
@ -502,7 +507,15 @@ public:
|
|||||||
|
|
||||||
void InitializeInterfaces();
|
void InitializeInterfaces();
|
||||||
|
|
||||||
Core::Memory::Memory& GetMemory() const;
|
Core::Memory::Memory& GetMemory() {
|
||||||
|
return m_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
|
||||||
|
|
||||||
|
Core::ExclusiveMonitor& GetExclusiveMonitor() const {
|
||||||
|
return *m_exclusive_monitor;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Overridden parent functions.
|
// Overridden parent functions.
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -49,14 +49,21 @@ public:
|
|||||||
bool IsSignaled() const override;
|
bool IsSignaled() const override;
|
||||||
void OnClientClosed();
|
void OnClientClosed();
|
||||||
|
|
||||||
/// TODO: flesh these out to match the real kernel
|
|
||||||
Result OnRequest(KSessionRequest* request);
|
Result OnRequest(KSessionRequest* request);
|
||||||
Result SendReply(bool is_hle = false);
|
Result SendReply(uintptr_t server_message, uintptr_t server_buffer_size,
|
||||||
Result ReceiveRequest(std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
|
KPhysicalAddress server_message_paddr, bool is_hle = false);
|
||||||
|
Result ReceiveRequest(uintptr_t server_message, uintptr_t server_buffer_size,
|
||||||
|
KPhysicalAddress server_message_paddr,
|
||||||
|
std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
|
||||||
std::weak_ptr<Service::SessionRequestManager> manager = {});
|
std::weak_ptr<Service::SessionRequestManager> manager = {});
|
||||||
|
|
||||||
Result SendReplyHLE() {
|
Result SendReplyHLE() {
|
||||||
return SendReply(true);
|
R_RETURN(this->SendReply(0, 0, 0, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ReceiveRequestHLE(std::shared_ptr<Service::HLERequestContext>* out_context,
|
||||||
|
std::weak_ptr<Service::SessionRequestManager> manager) {
|
||||||
|
R_RETURN(this->ReceiveRequest(0, 0, 0, out_context, manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -33,8 +33,7 @@ void KSession::Initialize(KClientPort* client_port, uintptr_t name) {
|
|||||||
m_name = name;
|
m_name = name;
|
||||||
|
|
||||||
// Set our owner process.
|
// Set our owner process.
|
||||||
//! FIXME: this is the wrong process!
|
m_process = GetCurrentProcessPointer(m_kernel);
|
||||||
m_process = m_kernel.ApplicationProcess();
|
|
||||||
m_process->Open();
|
m_process->Open();
|
||||||
|
|
||||||
// Set our port.
|
// Set our port.
|
||||||
|
@ -1422,8 +1422,7 @@ s32 GetCurrentCoreId(KernelCore& kernel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Core::Memory::Memory& GetCurrentMemory(KernelCore& kernel) {
|
Core::Memory::Memory& GetCurrentMemory(KernelCore& kernel) {
|
||||||
// TODO: per-process memory
|
return GetCurrentProcess(kernel).GetMemory();
|
||||||
return kernel.System().ApplicationMemory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KScopedDisableDispatch::~KScopedDisableDispatch() {
|
KScopedDisableDispatch::~KScopedDisableDispatch() {
|
||||||
|
@ -314,11 +314,7 @@ public:
|
|||||||
m_current_core_id = core;
|
m_current_core_id = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
KProcess* GetOwnerProcess() {
|
KProcess* GetOwnerProcess() const {
|
||||||
return m_parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
const KProcess* GetOwnerProcess() const {
|
|
||||||
return m_parent;
|
return m_parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_light_lock.h"
|
||||||
#include "core/hle/kernel/k_page_group.h"
|
#include "core/hle/kernel/k_page_group.h"
|
||||||
#include "core/hle/kernel/slab_helpers.h"
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
#include "core/hle/kernel/svc_types.h"
|
#include "core/hle/kernel/svc_types.h"
|
||||||
|
@ -68,8 +68,6 @@ struct KernelCore::Impl {
|
|||||||
|
|
||||||
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
|
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
|
||||||
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
|
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
|
||||||
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
|
|
||||||
global_handle_table->Initialize(KHandleTable::MaxTableSize);
|
|
||||||
|
|
||||||
is_phantom_mode_for_singlecore = false;
|
is_phantom_mode_for_singlecore = false;
|
||||||
|
|
||||||
@ -121,13 +119,8 @@ struct KernelCore::Impl {
|
|||||||
next_user_process_id = KProcess::ProcessIdMin;
|
next_user_process_id = KProcess::ProcessIdMin;
|
||||||
next_thread_id = 1;
|
next_thread_id = 1;
|
||||||
|
|
||||||
global_handle_table->Finalize();
|
|
||||||
global_handle_table.reset();
|
|
||||||
|
|
||||||
preemption_event = nullptr;
|
preemption_event = nullptr;
|
||||||
|
|
||||||
exclusive_monitor.reset();
|
|
||||||
|
|
||||||
// Cleanup persistent kernel objects
|
// Cleanup persistent kernel objects
|
||||||
auto CleanupObject = [](KAutoObject* obj) {
|
auto CleanupObject = [](KAutoObject* obj) {
|
||||||
if (obj) {
|
if (obj) {
|
||||||
@ -135,7 +128,6 @@ struct KernelCore::Impl {
|
|||||||
obj = nullptr;
|
obj = nullptr;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
CleanupObject(hid_shared_mem);
|
|
||||||
CleanupObject(font_shared_mem);
|
CleanupObject(font_shared_mem);
|
||||||
CleanupObject(irs_shared_mem);
|
CleanupObject(irs_shared_mem);
|
||||||
CleanupObject(time_shared_mem);
|
CleanupObject(time_shared_mem);
|
||||||
@ -192,8 +184,6 @@ struct KernelCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InitializePhysicalCores() {
|
void InitializePhysicalCores() {
|
||||||
exclusive_monitor =
|
|
||||||
Core::MakeExclusiveMonitor(system.ApplicationMemory(), Core::Hardware::NUM_CPU_CORES);
|
|
||||||
for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||||
const s32 core{static_cast<s32>(i)};
|
const s32 core{static_cast<s32>(i)};
|
||||||
|
|
||||||
@ -248,7 +238,7 @@ struct KernelCore::Impl {
|
|||||||
void InitializePreemption(KernelCore& kernel) {
|
void InitializePreemption(KernelCore& kernel) {
|
||||||
preemption_event = Core::Timing::CreateEvent(
|
preemption_event = Core::Timing::CreateEvent(
|
||||||
"PreemptionCallback",
|
"PreemptionCallback",
|
||||||
[this, &kernel](std::uintptr_t, s64 time,
|
[this, &kernel](s64 time,
|
||||||
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
|
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
|
||||||
{
|
{
|
||||||
KScopedSchedulerLock lock(kernel);
|
KScopedSchedulerLock lock(kernel);
|
||||||
@ -744,22 +734,16 @@ struct KernelCore::Impl {
|
|||||||
void InitializeHackSharedMemory(KernelCore& kernel) {
|
void InitializeHackSharedMemory(KernelCore& kernel) {
|
||||||
// Setup memory regions for emulated processes
|
// Setup memory regions for emulated processes
|
||||||
// TODO(bunnei): These should not be hardcoded regions initialized within the kernel
|
// TODO(bunnei): These should not be hardcoded regions initialized within the kernel
|
||||||
constexpr std::size_t hid_size{0x40000};
|
|
||||||
constexpr std::size_t font_size{0x1100000};
|
constexpr std::size_t font_size{0x1100000};
|
||||||
constexpr std::size_t irs_size{0x8000};
|
constexpr std::size_t irs_size{0x8000};
|
||||||
constexpr std::size_t time_size{0x1000};
|
constexpr std::size_t time_size{0x1000};
|
||||||
constexpr std::size_t hidbus_size{0x1000};
|
constexpr std::size_t hidbus_size{0x1000};
|
||||||
|
|
||||||
hid_shared_mem = KSharedMemory::Create(system.Kernel());
|
|
||||||
font_shared_mem = KSharedMemory::Create(system.Kernel());
|
font_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
irs_shared_mem = KSharedMemory::Create(system.Kernel());
|
irs_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
time_shared_mem = KSharedMemory::Create(system.Kernel());
|
time_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
|
hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
|
||||||
|
|
||||||
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
|
|
||||||
Svc::MemoryPermission::Read, hid_size);
|
|
||||||
KSharedMemory::Register(kernel, hid_shared_mem);
|
|
||||||
|
|
||||||
font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
|
font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
|
||||||
Svc::MemoryPermission::Read, font_size);
|
Svc::MemoryPermission::Read, font_size);
|
||||||
KSharedMemory::Register(kernel, font_shared_mem);
|
KSharedMemory::Register(kernel, font_shared_mem);
|
||||||
@ -798,10 +782,6 @@ struct KernelCore::Impl {
|
|||||||
|
|
||||||
std::shared_ptr<Core::Timing::EventType> preemption_event;
|
std::shared_ptr<Core::Timing::EventType> preemption_event;
|
||||||
|
|
||||||
// This is the kernel's handle table or supervisor handle table which
|
|
||||||
// stores all the objects in place.
|
|
||||||
std::unique_ptr<KHandleTable> global_handle_table;
|
|
||||||
|
|
||||||
std::unique_ptr<KAutoObjectWithListContainer> global_object_list_container;
|
std::unique_ptr<KAutoObjectWithListContainer> global_object_list_container;
|
||||||
|
|
||||||
std::unique_ptr<KObjectNameGlobalData> object_name_global_data;
|
std::unique_ptr<KObjectNameGlobalData> object_name_global_data;
|
||||||
@ -812,7 +792,6 @@ struct KernelCore::Impl {
|
|||||||
std::mutex server_lock;
|
std::mutex server_lock;
|
||||||
std::vector<std::unique_ptr<Service::ServerManager>> server_managers;
|
std::vector<std::unique_ptr<Service::ServerManager>> server_managers;
|
||||||
|
|
||||||
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
|
|
||||||
std::array<std::unique_ptr<Kernel::PhysicalCore>, Core::Hardware::NUM_CPU_CORES> cores;
|
std::array<std::unique_ptr<Kernel::PhysicalCore>, Core::Hardware::NUM_CPU_CORES> cores;
|
||||||
|
|
||||||
// Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
|
// Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
|
||||||
@ -889,10 +868,6 @@ KResourceLimit* KernelCore::GetSystemResourceLimit() {
|
|||||||
return impl->system_resource_limit;
|
return impl->system_resource_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
KScopedAutoObject<KThread> KernelCore::RetrieveThreadFromGlobalHandleTable(Handle handle) const {
|
|
||||||
return impl->global_handle_table->GetObject<KThread>(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KernelCore::AppendNewProcess(KProcess* process) {
|
void KernelCore::AppendNewProcess(KProcess* process) {
|
||||||
impl->process_list.push_back(process);
|
impl->process_list.push_back(process);
|
||||||
}
|
}
|
||||||
@ -966,14 +941,6 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
|
|||||||
return *impl->hardware_timer;
|
return *impl->hardware_timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {
|
|
||||||
return *impl->exclusive_monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
|
|
||||||
return *impl->exclusive_monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
|
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
|
||||||
return *impl->global_object_list_container;
|
return *impl->global_object_list_container;
|
||||||
}
|
}
|
||||||
@ -1037,14 +1004,6 @@ u64 KernelCore::CreateNewUserProcessID() {
|
|||||||
return impl->next_user_process_id++;
|
return impl->next_user_process_id++;
|
||||||
}
|
}
|
||||||
|
|
||||||
KHandleTable& KernelCore::GlobalHandleTable() {
|
|
||||||
return *impl->global_handle_table;
|
|
||||||
}
|
|
||||||
|
|
||||||
const KHandleTable& KernelCore::GlobalHandleTable() const {
|
|
||||||
return *impl->global_handle_table;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KernelCore::RegisterCoreThread(std::size_t core_id) {
|
void KernelCore::RegisterCoreThread(std::size_t core_id) {
|
||||||
impl->RegisterCoreThread(core_id);
|
impl->RegisterCoreThread(core_id);
|
||||||
}
|
}
|
||||||
@ -1190,14 +1149,6 @@ const KSystemResource& KernelCore::GetSystemSystemResource() const {
|
|||||||
return *impl->sys_system_resource;
|
return *impl->sys_system_resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::KSharedMemory& KernelCore::GetHidSharedMem() {
|
|
||||||
return *impl->hid_shared_mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Kernel::KSharedMemory& KernelCore::GetHidSharedMem() const {
|
|
||||||
return *impl->hid_shared_mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
Kernel::KSharedMemory& KernelCore::GetFontSharedMem() {
|
Kernel::KSharedMemory& KernelCore::GetFontSharedMem() {
|
||||||
return *impl->font_shared_mem;
|
return *impl->font_shared_mem;
|
||||||
}
|
}
|
||||||
|
@ -116,9 +116,6 @@ public:
|
|||||||
/// Retrieves a shared pointer to the system resource limit instance.
|
/// Retrieves a shared pointer to the system resource limit instance.
|
||||||
KResourceLimit* GetSystemResourceLimit();
|
KResourceLimit* GetSystemResourceLimit();
|
||||||
|
|
||||||
/// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table.
|
|
||||||
KScopedAutoObject<KThread> RetrieveThreadFromGlobalHandleTable(Handle handle) const;
|
|
||||||
|
|
||||||
/// Adds the given shared pointer to an internal list of active processes.
|
/// Adds the given shared pointer to an internal list of active processes.
|
||||||
void AppendNewProcess(KProcess* process);
|
void AppendNewProcess(KProcess* process);
|
||||||
|
|
||||||
@ -170,10 +167,6 @@ public:
|
|||||||
/// Stops execution of 'id' core, in order to reschedule a new thread.
|
/// Stops execution of 'id' core, in order to reschedule a new thread.
|
||||||
void PrepareReschedule(std::size_t id);
|
void PrepareReschedule(std::size_t id);
|
||||||
|
|
||||||
Core::ExclusiveMonitor& GetExclusiveMonitor();
|
|
||||||
|
|
||||||
const Core::ExclusiveMonitor& GetExclusiveMonitor() const;
|
|
||||||
|
|
||||||
KAutoObjectWithListContainer& ObjectListContainer();
|
KAutoObjectWithListContainer& ObjectListContainer();
|
||||||
|
|
||||||
const KAutoObjectWithListContainer& ObjectListContainer() const;
|
const KAutoObjectWithListContainer& ObjectListContainer() const;
|
||||||
@ -239,12 +232,6 @@ public:
|
|||||||
/// Gets the system resource manager.
|
/// Gets the system resource manager.
|
||||||
const KSystemResource& GetSystemSystemResource() const;
|
const KSystemResource& GetSystemSystemResource() const;
|
||||||
|
|
||||||
/// Gets the shared memory object for HID services.
|
|
||||||
Kernel::KSharedMemory& GetHidSharedMem();
|
|
||||||
|
|
||||||
/// Gets the shared memory object for HID services.
|
|
||||||
const Kernel::KSharedMemory& GetHidSharedMem() const;
|
|
||||||
|
|
||||||
/// Gets the shared memory object for font services.
|
/// Gets the shared memory object for font services.
|
||||||
Kernel::KSharedMemory& GetFontSharedMem();
|
Kernel::KSharedMemory& GetFontSharedMem();
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@ public:
|
|||||||
static constexpr inline u64 NullTag = 0;
|
static constexpr inline u64 NullTag = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum class ReceiveListCountType : u32 {
|
enum ReceiveListCountType : u32 {
|
||||||
None = 0,
|
ReceiveListCountType_None = 0,
|
||||||
ToMessageBuffer = 1,
|
ReceiveListCountType_ToMessageBuffer = 1,
|
||||||
ToSingleBuffer = 2,
|
ReceiveListCountType_ToSingleBuffer = 2,
|
||||||
|
|
||||||
CountOffset = 2,
|
ReceiveListCountType_CountOffset = 2,
|
||||||
CountMax = 13,
|
ReceiveListCountType_CountMax = 13,
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -591,16 +591,16 @@ public:
|
|||||||
// Add the size of the receive list.
|
// Add the size of the receive list.
|
||||||
const auto count = hdr.GetReceiveListCount();
|
const auto count = hdr.GetReceiveListCount();
|
||||||
switch (count) {
|
switch (count) {
|
||||||
case MessageHeader::ReceiveListCountType::None:
|
case MessageHeader::ReceiveListCountType_None:
|
||||||
break;
|
break;
|
||||||
case MessageHeader::ReceiveListCountType::ToMessageBuffer:
|
case MessageHeader::ReceiveListCountType_ToMessageBuffer:
|
||||||
break;
|
break;
|
||||||
case MessageHeader::ReceiveListCountType::ToSingleBuffer:
|
case MessageHeader::ReceiveListCountType_ToSingleBuffer:
|
||||||
msg_size += ReceiveListEntry::GetDataSize();
|
msg_size += ReceiveListEntry::GetDataSize();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
msg_size += (static_cast<s32>(count) -
|
msg_size += (static_cast<s32>(count) -
|
||||||
static_cast<s32>(MessageHeader::ReceiveListCountType::CountOffset)) *
|
static_cast<s32>(MessageHeader::ReceiveListCountType_CountOffset)) *
|
||||||
ReceiveListEntry::GetDataSize();
|
ReceiveListEntry::GetDataSize();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,6 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|
||||||
case InfoType::IsApplication:
|
case InfoType::IsApplication:
|
||||||
LOG_WARNING(Kernel_SVC, "(STUBBED) Assuming process is application");
|
|
||||||
*result = process->IsApplication();
|
*result = process->IsApplication();
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|
||||||
|
@ -48,8 +48,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send the reply.
|
// Send the reply.
|
||||||
R_TRY(session->SendReply());
|
R_TRY(session->SendReply(message, buffer_size, message_paddr));
|
||||||
// R_TRY(session->SendReply(message, buffer_size, message_paddr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive a message.
|
// Receive a message.
|
||||||
@ -85,8 +84,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes
|
|||||||
if (R_SUCCEEDED(result)) {
|
if (R_SUCCEEDED(result)) {
|
||||||
KServerSession* session = objs[index]->DynamicCast<KServerSession*>();
|
KServerSession* session = objs[index]->DynamicCast<KServerSession*>();
|
||||||
if (session != nullptr) {
|
if (session != nullptr) {
|
||||||
// result = session->ReceiveRequest(message, buffer_size, message_paddr);
|
result = session->ReceiveRequest(message, buffer_size, message_paddr);
|
||||||
result = session->ReceiveRequest();
|
|
||||||
if (ResultNotFound == result) {
|
if (ResultNotFound == result) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,9 @@ constexpr Result ResultInvalidState{ErrorModule::Kernel, 125};
|
|||||||
constexpr Result ResultReservedUsed{ErrorModule::Kernel, 126};
|
constexpr Result ResultReservedUsed{ErrorModule::Kernel, 126};
|
||||||
constexpr Result ResultPortClosed{ErrorModule::Kernel, 131};
|
constexpr Result ResultPortClosed{ErrorModule::Kernel, 131};
|
||||||
constexpr Result ResultLimitReached{ErrorModule::Kernel, 132};
|
constexpr Result ResultLimitReached{ErrorModule::Kernel, 132};
|
||||||
|
constexpr Result ResultReceiveListBroken{ErrorModule::Kernel, 258};
|
||||||
constexpr Result ResultOutOfAddressSpace{ErrorModule::Kernel, 259};
|
constexpr Result ResultOutOfAddressSpace{ErrorModule::Kernel, 259};
|
||||||
|
constexpr Result ResultMessageTooLarge{ErrorModule::Kernel, 260};
|
||||||
constexpr Result ResultInvalidId{ErrorModule::Kernel, 519};
|
constexpr Result ResultInvalidId{ErrorModule::Kernel, 519};
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user