mirror of
https://github.com/yuzu-emu/yuzu-android
synced 2024-12-23 12:01:20 -08:00
kernel: instantiate memory separately for each guest process
This commit is contained in:
parent
91290b9be4
commit
419055e484
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -370,7 +370,7 @@ 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)),
|
||||||
|
@ -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;
|
||||||
@ -399,7 +399,7 @@ 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} {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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"
|
||||||
@ -130,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}, profile_manager{},
|
reporter{system}, applet_manager{system}, profile_manager{}, time_manager{system} {}
|
||||||
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>();
|
||||||
@ -241,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();
|
||||||
|
|
||||||
@ -300,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);
|
||||||
@ -323,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) {
|
||||||
@ -426,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()) {
|
||||||
@ -507,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;
|
||||||
|
|
||||||
@ -567,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -652,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() {
|
||||||
@ -723,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() {
|
||||||
|
@ -116,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;
|
||||||
@ -225,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;
|
||||||
@ -250,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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1128,7 +1128,8 @@ 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,
|
||||||
@ -1235,7 +1236,11 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::InitializeInterfaces() {
|
void KProcess::InitializeInterfaces() {
|
||||||
|
m_exclusive_monitor =
|
||||||
|
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
|
||||||
|
|
||||||
this->GetMemory().SetCurrentPageTable(*this);
|
this->GetMemory().SetCurrentPageTable(*this);
|
||||||
|
this->GetMemory().SetGPUDirtyManagers(m_dirty_memory_managers);
|
||||||
|
|
||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
if (this->Is64Bit() && Settings::IsNceEnabled()) {
|
if (this->Is64Bit() && Settings::IsNceEnabled()) {
|
||||||
@ -1248,13 +1253,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 +1310,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.
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +126,6 @@ struct KernelCore::Impl {
|
|||||||
|
|
||||||
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) {
|
||||||
@ -191,8 +189,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)};
|
||||||
|
|
||||||
@ -805,7 +801,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
|
||||||
@ -959,14 +954,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;
|
||||||
}
|
}
|
||||||
|
@ -170,10 +170,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;
|
||||||
|
Loading…
Reference in New Issue
Block a user