SVC: use context and generic templates
This commit is contained in:
parent
28513c5177
commit
aec8b1e375
|
@ -116,7 +116,6 @@ add_library(core STATIC
|
|||
hle/kernel/errors.h
|
||||
hle/kernel/event.cpp
|
||||
hle/kernel/event.h
|
||||
hle/kernel/function_wrappers.h
|
||||
hle/kernel/handle_table.cpp
|
||||
hle/kernel/handle_table.h
|
||||
hle/kernel/hle_ipc.cpp
|
||||
|
@ -148,6 +147,7 @@ add_library(core STATIC
|
|||
hle/kernel/shared_page.h
|
||||
hle/kernel/svc.cpp
|
||||
hle/kernel/svc.h
|
||||
hle/kernel/svc_wrapper.h
|
||||
hle/kernel/thread.cpp
|
||||
hle/kernel/thread.h
|
||||
hle/kernel/timer.cpp
|
||||
|
|
|
@ -72,7 +72,7 @@ private:
|
|||
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
|
||||
public:
|
||||
explicit DynarmicUserCallbacks(ARM_Dynarmic& parent)
|
||||
: parent(parent), timing(parent.system.CoreTiming()) {}
|
||||
: parent(parent), timing(parent.system.CoreTiming()), svc_context(parent.system) {}
|
||||
~DynarmicUserCallbacks() = default;
|
||||
|
||||
std::uint8_t MemoryRead8(VAddr vaddr) override {
|
||||
|
@ -123,7 +123,7 @@ public:
|
|||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
Kernel::CallSVC(swi);
|
||||
svc_context.CallSVC(swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(VAddr pc, Dynarmic::A32::Exception exception) override {
|
||||
|
@ -158,6 +158,7 @@ public:
|
|||
|
||||
ARM_Dynarmic& parent;
|
||||
Core::Timing& timing;
|
||||
Kernel::SVC svc_context;
|
||||
};
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(Core::System& system, PrivilegeMode initial_mode)
|
||||
|
|
|
@ -3864,7 +3864,7 @@ SWI_INST : {
|
|||
cpu->NumInstrsToExecute =
|
||||
num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs;
|
||||
num_instrs = 0;
|
||||
Kernel::CallSVC(inst_cream->num & 0xFFFF);
|
||||
Kernel::SVC{Core::System::GetInstance()}.CallSVC(inst_cream->num & 0xFFFF);
|
||||
// The kernel would call ERET to get here, which clears exclusive memory state.
|
||||
cpu->UnsetExclusiveMemoryAddress();
|
||||
}
|
||||
|
|
|
@ -172,6 +172,8 @@ System::ResultStatus System::Init(EmuWindow& emu_window, u32 system_mode) {
|
|||
|
||||
timing = std::make_unique<Timing>();
|
||||
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(system_mode);
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
cpu_core = std::make_unique<ARM_Dynarmic>(*this, USER32MODE);
|
||||
|
@ -197,7 +199,6 @@ System::ResultStatus System::Init(EmuWindow& emu_window, u32 system_mode) {
|
|||
archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
|
||||
|
||||
HW::Init();
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(system_mode);
|
||||
Service::Init(*this);
|
||||
GDBStub::Init();
|
||||
|
||||
|
|
|
@ -1,265 +0,0 @@
|
|||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace HLE {
|
||||
|
||||
static inline u32 Param(int n) {
|
||||
return Core::CPU().GetReg(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* HLE a function return from the current ARM11 userland process
|
||||
* @param res Result to return
|
||||
*/
|
||||
static inline void FuncReturn(u32 res) {
|
||||
Core::CPU().SetReg(0, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* HLE a function return (64-bit) from the current ARM11 userland process
|
||||
* @param res Result to return (64-bit)
|
||||
* @todo Verify that this function is correct
|
||||
*/
|
||||
static inline void FuncReturn64(u64 res) {
|
||||
Core::CPU().SetReg(0, (u32)(res & 0xFFFFFFFF));
|
||||
Core::CPU().SetReg(1, (u32)((res >> 32) & 0xFFFFFFFF));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type ResultCode
|
||||
|
||||
template <ResultCode func(u32, u32, u32, u32)>
|
||||
void Wrap() {
|
||||
FuncReturn(func(Param(0), Param(1), Param(2), Param(3)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u32, u32, u32, u32, u32)>
|
||||
void Wrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(0), Param(1), Param(2), Param(3), Param(4)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u32, u32, u32, u32, s32)>
|
||||
void Wrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(0), Param(1), Param(2), Param(3), Param(4)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(s32*, VAddr, s32, bool, s64)>
|
||||
void Wrap() {
|
||||
s32 param_1 = 0;
|
||||
s32 retval =
|
||||
func(¶m_1, Param(1), (s32)Param(2), (Param(3) != 0), (((s64)Param(4) << 32) | Param(0)))
|
||||
.raw;
|
||||
|
||||
Core::CPU().SetReg(1, (u32)param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(s32*, VAddr, s32, u32)>
|
||||
void Wrap() {
|
||||
s32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1), (s32)Param(2), Param(3)).raw;
|
||||
|
||||
Core::CPU().SetReg(1, (u32)param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u32, u32, u32, s64)>
|
||||
void Wrap() {
|
||||
FuncReturn(
|
||||
func(Param(0), Param(1), Param(2), Param(3), (((s64)Param(5) << 32) | Param(4))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*)>
|
||||
void Wrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, s64)>
|
||||
void Wrap() {
|
||||
s32 retval = func(Param(0), (((s64)Param(3) << 32) | Param(2))).raw;
|
||||
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Kernel::MemoryInfo*, Kernel::PageInfo*, u32)>
|
||||
void Wrap() {
|
||||
Kernel::MemoryInfo memory_info = {};
|
||||
Kernel::PageInfo page_info = {};
|
||||
u32 retval = func(&memory_info, &page_info, Param(2)).raw;
|
||||
Core::CPU().SetReg(1, memory_info.base_address);
|
||||
Core::CPU().SetReg(2, memory_info.size);
|
||||
Core::CPU().SetReg(3, memory_info.permission);
|
||||
Core::CPU().SetReg(4, memory_info.state);
|
||||
Core::CPU().SetReg(5, page_info.flags);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Kernel::MemoryInfo*, Kernel::PageInfo*, Kernel::Handle, u32)>
|
||||
void Wrap() {
|
||||
Kernel::MemoryInfo memory_info = {};
|
||||
Kernel::PageInfo page_info = {};
|
||||
u32 retval = func(&memory_info, &page_info, Param(2), Param(3)).raw;
|
||||
Core::CPU().SetReg(1, memory_info.base_address);
|
||||
Core::CPU().SetReg(2, memory_info.size);
|
||||
Core::CPU().SetReg(3, memory_info.permission);
|
||||
Core::CPU().SetReg(4, memory_info.state);
|
||||
Core::CPU().SetReg(5, page_info.flags);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(s32*, u32)>
|
||||
void Wrap() {
|
||||
s32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, s32)>
|
||||
void Wrap() {
|
||||
FuncReturn(func(Param(0), (s32)Param(1)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u32)>
|
||||
void Wrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32)>
|
||||
void Wrap() {
|
||||
FuncReturn(func(Param(0)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, s32, s32)>
|
||||
void Wrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(s32*, u32, s32)>
|
||||
void Wrap() {
|
||||
s32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(s64*, u32, s32)>
|
||||
void Wrap() {
|
||||
s64 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2)).raw;
|
||||
Core::CPU().SetReg(1, (u32)param_1);
|
||||
Core::CPU().SetReg(2, (u32)(param_1 >> 32));
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u32, u32, u32, u32)>
|
||||
void Wrap() {
|
||||
u32 param_1 = 0;
|
||||
// The last parameter is passed in R0 instead of R4
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), Param(3), Param(0)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, s64, s64)>
|
||||
void Wrap() {
|
||||
s64 param1 = ((u64)Param(3) << 32) | Param(2);
|
||||
s64 param2 = ((u64)Param(4) << 32) | Param(1);
|
||||
FuncReturn(func(Param(0), param1, param2).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(s64*, Kernel::Handle, u32)>
|
||||
void Wrap() {
|
||||
s64 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2)).raw;
|
||||
Core::CPU().SetReg(1, (u32)param_1);
|
||||
Core::CPU().SetReg(2, (u32)(param_1 >> 32));
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Kernel::Handle, u32)>
|
||||
void Wrap() {
|
||||
FuncReturn(func(Param(0), Param(1)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Kernel::Handle*, Kernel::Handle*, VAddr, u32)>
|
||||
void Wrap() {
|
||||
Kernel::Handle param_1 = 0;
|
||||
Kernel::Handle param_2 = 0;
|
||||
u32 retval = func(¶m_1, ¶m_2, Param(2), Param(3)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CPU().SetReg(2, param_2);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Kernel::Handle*, Kernel::Handle*)>
|
||||
void Wrap() {
|
||||
Kernel::Handle param_1 = 0;
|
||||
Kernel::Handle param_2 = 0;
|
||||
u32 retval = func(¶m_1, ¶m_2).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CPU().SetReg(2, param_2);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type u32
|
||||
|
||||
template <u32 func()>
|
||||
void Wrap() {
|
||||
FuncReturn(func());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type s64
|
||||
|
||||
template <s64 func()>
|
||||
void Wrap() {
|
||||
FuncReturn64(func());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Function wrappers that return type void
|
||||
|
||||
template <void func(s64)>
|
||||
void Wrap() {
|
||||
func(((s64)Param(1) << 32) | Param(0));
|
||||
}
|
||||
|
||||
template <void func(VAddr, int len)>
|
||||
void Wrap() {
|
||||
func(Param(0), Param(1));
|
||||
}
|
||||
|
||||
template <void func(u8)>
|
||||
void Wrap() {
|
||||
func((u8)Param(0));
|
||||
}
|
||||
|
||||
} // namespace HLE
|
|
@ -10,13 +10,13 @@
|
|||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/function_wrappers.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/ipc.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
|
@ -28,6 +28,7 @@
|
|||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/timer.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
|
@ -56,7 +57,7 @@ enum ControlMemoryOperation {
|
|||
};
|
||||
|
||||
/// Map application or GSP heap memory
|
||||
static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 addr1, u32 size,
|
||||
ResultCode SVC::ControlMemory(u32* out_addr, u32 addr0, u32 addr1, u32 size, u32 operation,
|
||||
u32 permissions) {
|
||||
LOG_DEBUG(Kernel_SVC,
|
||||
"called operation=0x{:08X}, addr0=0x{:08X}, addr1=0x{:08X}, "
|
||||
|
@ -83,7 +84,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
|
|||
}
|
||||
VMAPermission vma_permissions = (VMAPermission)permissions;
|
||||
|
||||
auto& process = *Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
auto& process = *kernel.GetCurrentProcess();
|
||||
|
||||
switch (operation & MEMOP_OPERATION_MASK) {
|
||||
case MEMOP_FREE: {
|
||||
|
@ -140,8 +141,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static void ExitProcess() {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
void SVC::ExitProcess() {
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->process_id);
|
||||
|
||||
|
@ -169,19 +169,18 @@ static void ExitProcess() {
|
|||
// Kill the current thread
|
||||
kernel.GetThreadManager().GetCurrentThread()->Stop();
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Maps a memory block to specified address
|
||||
static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) {
|
||||
ResultCode SVC::MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called memblock=0x{:08X}, addr=0x{:08X}, mypermissions=0x{:08X}, "
|
||||
"otherpermission={}",
|
||||
handle, addr, permissions, other_permissions);
|
||||
|
||||
SharedPtr<SharedMemory> shared_memory =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<SharedMemory>(
|
||||
handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<SharedMemory>(handle);
|
||||
if (shared_memory == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -195,8 +194,7 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o
|
|||
case MemoryPermission::WriteExecute:
|
||||
case MemoryPermission::ReadWriteExecute:
|
||||
case MemoryPermission::DontCare:
|
||||
return shared_memory->Map(Core::System::GetInstance().Kernel().GetCurrentProcess().get(),
|
||||
addr, permissions_type,
|
||||
return shared_memory->Map(kernel.GetCurrentProcess().get(), addr, permissions_type,
|
||||
static_cast<MemoryPermission>(other_permissions));
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions);
|
||||
|
@ -205,12 +203,12 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o
|
|||
return ERR_INVALID_COMBINATION;
|
||||
}
|
||||
|
||||
static ResultCode UnmapMemoryBlock(Handle handle, u32 addr) {
|
||||
ResultCode SVC::UnmapMemoryBlock(Handle handle, u32 addr) {
|
||||
LOG_TRACE(Kernel_SVC, "called memblock=0x{:08X}, addr=0x{:08X}", handle, addr);
|
||||
|
||||
// TODO(Subv): Return E0A01BF5 if the address is not in the application's heap
|
||||
|
||||
SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
SharedPtr<SharedMemory> shared_memory = current_process->handle_table.Get<SharedMemory>(handle);
|
||||
if (shared_memory == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
@ -219,7 +217,7 @@ static ResultCode UnmapMemoryBlock(Handle handle, u32 addr) {
|
|||
}
|
||||
|
||||
/// Connect to an OS service given the port name, returns the handle to the port to out
|
||||
static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) {
|
||||
ResultCode SVC::ConnectToPort(Handle* out_handle, VAddr port_name_address) {
|
||||
if (!Memory::IsValidVirtualAddress(port_name_address))
|
||||
return ERR_NOT_FOUND;
|
||||
|
||||
|
@ -231,8 +229,6 @@ static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) {
|
|||
|
||||
LOG_TRACE(Kernel_SVC, "called port_name={}", port_name);
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
auto it = kernel.named_ports.find(port_name);
|
||||
if (it == kernel.named_ports.end()) {
|
||||
LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name);
|
||||
|
@ -250,8 +246,7 @@ static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) {
|
|||
}
|
||||
|
||||
/// Makes a blocking IPC call to an OS service.
|
||||
static ResultCode SendSyncRequest(Handle handle) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::SendSyncRequest(Handle handle) {
|
||||
SharedPtr<ClientSession> session =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<ClientSession>(handle);
|
||||
if (session == nullptr) {
|
||||
|
@ -260,20 +255,19 @@ static ResultCode SendSyncRequest(Handle handle) {
|
|||
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
return session->SendSyncRequest(kernel.GetThreadManager().GetCurrentThread());
|
||||
}
|
||||
|
||||
/// Close a handle
|
||||
static ResultCode CloseHandle(Handle handle) {
|
||||
ResultCode SVC::CloseHandle(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
|
||||
return Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Close(handle);
|
||||
return kernel.GetCurrentProcess()->handle_table.Close(handle);
|
||||
}
|
||||
|
||||
/// Wait for a handle to synchronize, timeout after the specified nanoseconds
|
||||
static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
||||
auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle);
|
||||
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
|
||||
|
||||
|
@ -311,7 +305,7 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
|||
// don't have to do anything else here.
|
||||
};
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread
|
||||
// resumes due to a signal in its wait objects.
|
||||
|
@ -325,9 +319,8 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
|||
}
|
||||
|
||||
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
|
||||
static ResultCode WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count,
|
||||
ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count,
|
||||
bool wait_all, s64 nano_seconds) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(handles_address))
|
||||
|
@ -400,7 +393,7 @@ static ResultCode WaitSynchronizationN(s32* out, VAddr handles_address, s32 hand
|
|||
// The wait_all case does not update the output index.
|
||||
};
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
// This value gets set to -1 by default in this case, it is not modified after this.
|
||||
*out = -1;
|
||||
|
@ -460,7 +453,7 @@ static ResultCode WaitSynchronizationN(s32* out, VAddr handles_address, s32 hand
|
|||
thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
|
||||
};
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
|
||||
// signal in one of its wait objects.
|
||||
|
@ -498,7 +491,7 @@ static ResultCode ReceiveIPCRequest(SharedPtr<ServerSession> server_session,
|
|||
}
|
||||
|
||||
/// In a single operation, sends a IPC reply and waits for a new request.
|
||||
static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_count,
|
||||
ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_count,
|
||||
Handle reply_target) {
|
||||
if (!Memory::IsValidVirtualAddress(handles_address))
|
||||
return ERR_INVALID_POINTER;
|
||||
|
@ -510,7 +503,6 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_
|
|||
using ObjectPtr = SharedPtr<WaitObject>;
|
||||
std::vector<ObjectPtr> objects(handle_count);
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
|
||||
for (int i = 0; i < handle_count; ++i) {
|
||||
|
@ -614,7 +606,7 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_
|
|||
thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
|
||||
};
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
|
||||
// signal in one of its wait objects, or to 0xC8A01836 if there was a translation error.
|
||||
|
@ -624,8 +616,7 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_
|
|||
}
|
||||
|
||||
/// Create an address arbiter (to allocate access to shared resources)
|
||||
static ResultCode CreateAddressArbiter(Handle* out_handle) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::CreateAddressArbiter(Handle* out_handle) {
|
||||
SharedPtr<AddressArbiter> arbiter = kernel.CreateAddressArbiter();
|
||||
CASCADE_RESULT(*out_handle,
|
||||
kernel.GetCurrentProcess()->handle_table.Create(std::move(arbiter)));
|
||||
|
@ -634,13 +625,10 @@ static ResultCode CreateAddressArbiter(Handle* out_handle) {
|
|||
}
|
||||
|
||||
/// Arbitrate address
|
||||
static ResultCode ArbitrateAddress(Handle handle, u32 address, u32 type, u32 value,
|
||||
s64 nanoseconds) {
|
||||
ResultCode SVC::ArbitrateAddress(Handle handle, u32 address, u32 type, u32 value, s64 nanoseconds) {
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}, address=0x{:08X}, type=0x{:08X}, value=0x{:08X}",
|
||||
handle, address, type, value);
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
SharedPtr<AddressArbiter> arbiter =
|
||||
kernel.GetCurrentProcess()->handle_table.Get<AddressArbiter>(handle);
|
||||
if (arbiter == nullptr)
|
||||
|
@ -651,12 +639,12 @@ static ResultCode ArbitrateAddress(Handle handle, u32 address, u32 type, u32 val
|
|||
static_cast<ArbitrationType>(type), address, value, nanoseconds);
|
||||
|
||||
// TODO(Subv): Identify in which specific cases this call should cause a reschedule.
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void Break(u8 break_reason) {
|
||||
void SVC::Break(u8 break_reason) {
|
||||
LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!");
|
||||
std::string reason_str;
|
||||
switch (break_reason) {
|
||||
|
@ -677,7 +665,7 @@ static void Break(u8 break_reason) {
|
|||
}
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
static void OutputDebugString(VAddr address, int len) {
|
||||
void SVC::OutputDebugString(VAddr address, s32 len) {
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -688,10 +676,10 @@ static void OutputDebugString(VAddr address, int len) {
|
|||
}
|
||||
|
||||
/// Get resource limit
|
||||
static ResultCode GetResourceLimit(Handle* resource_limit, Handle process_handle) {
|
||||
ResultCode SVC::GetResourceLimit(Handle* resource_limit, Handle process_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
|
||||
|
||||
SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
SharedPtr<Process> process = current_process->handle_table.Get<Process>(process_handle);
|
||||
if (process == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
@ -702,14 +690,13 @@ static ResultCode GetResourceLimit(Handle* resource_limit, Handle process_handle
|
|||
}
|
||||
|
||||
/// Get resource limit current values
|
||||
static ResultCode GetResourceLimitCurrentValues(VAddr values, Handle resource_limit_handle,
|
||||
ResultCode SVC::GetResourceLimitCurrentValues(VAddr values, Handle resource_limit_handle,
|
||||
VAddr names, u32 name_count) {
|
||||
LOG_TRACE(Kernel_SVC, "called resource_limit={:08X}, names={:08X}, name_count={}",
|
||||
resource_limit_handle, names, name_count);
|
||||
|
||||
SharedPtr<ResourceLimit> resource_limit =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<ResourceLimit>(
|
||||
resource_limit_handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<ResourceLimit>(resource_limit_handle);
|
||||
if (resource_limit == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -723,14 +710,13 @@ static ResultCode GetResourceLimitCurrentValues(VAddr values, Handle resource_li
|
|||
}
|
||||
|
||||
/// Get resource limit max values
|
||||
static ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limit_handle,
|
||||
VAddr names, u32 name_count) {
|
||||
ResultCode SVC::GetResourceLimitLimitValues(VAddr values, Handle resource_limit_handle, VAddr names,
|
||||
u32 name_count) {
|
||||
LOG_TRACE(Kernel_SVC, "called resource_limit={:08X}, names={:08X}, name_count={}",
|
||||
resource_limit_handle, names, name_count);
|
||||
|
||||
SharedPtr<ResourceLimit> resource_limit =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<ResourceLimit>(
|
||||
resource_limit_handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<ResourceLimit>(resource_limit_handle);
|
||||
if (resource_limit == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -744,15 +730,15 @@ static ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limi
|
|||
}
|
||||
|
||||
/// Creates a new thread
|
||||
static ResultCode CreateThread(Handle* out_handle, u32 priority, u32 entry_point, u32 arg,
|
||||
u32 stack_top, s32 processor_id) {
|
||||
ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr stack_top,
|
||||
u32 priority, s32 processor_id) {
|
||||
std::string name = fmt::format("thread-{:08X}", entry_point);
|
||||
|
||||
if (priority > ThreadPrioLowest) {
|
||||
return ERR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
|
||||
SharedPtr<ResourceLimit>& resource_limit = current_process->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
|
@ -782,16 +768,16 @@ static ResultCode CreateThread(Handle* out_handle, u32 priority, u32 entry_point
|
|||
break;
|
||||
}
|
||||
|
||||
CASCADE_RESULT(SharedPtr<Thread> thread, Core::System::GetInstance().Kernel().CreateThread(
|
||||
name, entry_point, priority, arg, processor_id,
|
||||
stack_top, *current_process));
|
||||
CASCADE_RESULT(SharedPtr<Thread> thread,
|
||||
kernel.CreateThread(name, entry_point, priority, arg, processor_id, stack_top,
|
||||
*current_process));
|
||||
|
||||
thread->context->SetFpscr(FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO |
|
||||
FPSCR_ROUND_TOZERO); // 0x03C00000
|
||||
|
||||
CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(thread)));
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
|
||||
|
@ -802,17 +788,16 @@ static ResultCode CreateThread(Handle* out_handle, u32 priority, u32 entry_point
|
|||
}
|
||||
|
||||
/// Called when a thread exits
|
||||
static void ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CPU().GetPC());
|
||||
void SVC::ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CPU().GetPC());
|
||||
|
||||
Core::System::GetInstance().Kernel().GetThreadManager().ExitCurrentThread();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
kernel.GetThreadManager().ExitCurrentThread();
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Gets the priority for the specified thread
|
||||
static ResultCode GetThreadPriority(u32* priority, Handle handle) {
|
||||
const SharedPtr<Thread> thread =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(handle);
|
||||
ResultCode SVC::GetThreadPriority(u32* priority, Handle handle) {
|
||||
const SharedPtr<Thread> thread = kernel.GetCurrentProcess()->handle_table.Get<Thread>(handle);
|
||||
if (thread == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -821,20 +806,18 @@ static ResultCode GetThreadPriority(u32* priority, Handle handle) {
|
|||
}
|
||||
|
||||
/// Sets the priority for the specified thread
|
||||
static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
ResultCode SVC::SetThreadPriority(Handle handle, u32 priority) {
|
||||
if (priority > ThreadPrioLowest) {
|
||||
return ERR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
SharedPtr<Thread> thread =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(handle);
|
||||
SharedPtr<Thread> thread = kernel.GetCurrentProcess()->handle_table.Get<Thread>(handle);
|
||||
if (thread == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
// Note: The kernel uses the current process's resource limit instead of
|
||||
// the one from the thread owner's resource limit.
|
||||
SharedPtr<ResourceLimit>& resource_limit =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->resource_limit;
|
||||
SharedPtr<ResourceLimit>& resource_limit = kernel.GetCurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
@ -846,15 +829,14 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
|||
for (auto& mutex : thread->pending_mutexes)
|
||||
mutex->UpdatePriority();
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Create a mutex
|
||||
static ResultCode CreateMutex(Handle* out_handle, u32 initial_locked) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) {
|
||||
SharedPtr<Mutex> mutex = kernel.CreateMutex(initial_locked != 0);
|
||||
mutex->name = fmt::format("mutex-{:08x}", Core::CPU().GetReg(14));
|
||||
mutex->name = fmt::format("mutex-{:08x}", system.CPU().GetReg(14));
|
||||
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(mutex)));
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}",
|
||||
|
@ -864,11 +846,9 @@ static ResultCode CreateMutex(Handle* out_handle, u32 initial_locked) {
|
|||
}
|
||||
|
||||
/// Release a mutex
|
||||
static ResultCode ReleaseMutex(Handle handle) {
|
||||
ResultCode SVC::ReleaseMutex(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}", handle);
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
SharedPtr<Mutex> mutex = kernel.GetCurrentProcess()->handle_table.Get<Mutex>(handle);
|
||||
if (mutex == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
@ -877,12 +857,11 @@ static ResultCode ReleaseMutex(Handle handle) {
|
|||
}
|
||||
|
||||
/// Get the ID of the specified process
|
||||
static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
|
||||
ResultCode SVC::GetProcessId(u32* process_id, Handle process_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
|
||||
|
||||
const SharedPtr<Process> process =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Process>(
|
||||
process_handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(process_handle);
|
||||
if (process == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -891,12 +870,11 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
|
|||
}
|
||||
|
||||
/// Get the ID of the process that owns the specified thread
|
||||
static ResultCode GetProcessIdOfThread(u32* process_id, Handle thread_handle) {
|
||||
ResultCode SVC::GetProcessIdOfThread(u32* process_id, Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
|
||||
|
||||
const SharedPtr<Thread> thread =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(
|
||||
thread_handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Thread>(thread_handle);
|
||||
if (thread == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -909,11 +887,10 @@ static ResultCode GetProcessIdOfThread(u32* process_id, Handle thread_handle) {
|
|||
}
|
||||
|
||||
/// Get the ID for the specified thread.
|
||||
static ResultCode GetThreadId(u32* thread_id, Handle handle) {
|
||||
ResultCode SVC::GetThreadId(u32* thread_id, Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", handle);
|
||||
|
||||
const SharedPtr<Thread> thread =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(handle);
|
||||
const SharedPtr<Thread> thread = kernel.GetCurrentProcess()->handle_table.Get<Thread>(handle);
|
||||
if (thread == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -922,11 +899,10 @@ static ResultCode GetThreadId(u32* thread_id, Handle handle) {
|
|||
}
|
||||
|
||||
/// Creates a semaphore
|
||||
static ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) {
|
||||
CASCADE_RESULT(SharedPtr<Semaphore> semaphore,
|
||||
kernel.CreateSemaphore(initial_count, max_count));
|
||||
semaphore->name = fmt::format("semaphore-{:08x}", Core::CPU().GetReg(14));
|
||||
semaphore->name = fmt::format("semaphore-{:08x}", system.CPU().GetReg(14));
|
||||
CASCADE_RESULT(*out_handle,
|
||||
kernel.GetCurrentProcess()->handle_table.Create(std::move(semaphore)));
|
||||
|
||||
|
@ -936,12 +912,11 @@ static ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max
|
|||
}
|
||||
|
||||
/// Releases a certain number of slots in a semaphore
|
||||
static ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count) {
|
||||
ResultCode SVC::ReleaseSemaphore(s32* count, Handle handle, s32 release_count) {
|
||||
LOG_TRACE(Kernel_SVC, "called release_count={}, handle=0x{:08X}", release_count, handle);
|
||||
|
||||
SharedPtr<Semaphore> semaphore =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Semaphore>(
|
||||
handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Semaphore>(handle);
|
||||
if (semaphore == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -951,11 +926,10 @@ static ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count)
|
|||
}
|
||||
|
||||
/// Query process memory
|
||||
static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info,
|
||||
ResultCode SVC::QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info,
|
||||
Handle process_handle, u32 addr) {
|
||||
SharedPtr<Process> process =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Process>(
|
||||
process_handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(process_handle);
|
||||
if (process == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -975,15 +949,14 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_inf
|
|||
}
|
||||
|
||||
/// Query memory
|
||||
static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 addr) {
|
||||
ResultCode SVC::QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 addr) {
|
||||
return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr);
|
||||
}
|
||||
|
||||
/// Create an event
|
||||
static ResultCode CreateEvent(Handle* out_handle, u32 reset_type) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) {
|
||||
SharedPtr<Event> evt = kernel.CreateEvent(static_cast<ResetType>(reset_type),
|
||||
fmt::format("event-{:08x}", Core::CPU().GetReg(14)));
|
||||
fmt::format("event-{:08x}", system.CPU().GetReg(14)));
|
||||
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(evt)));
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
|
||||
|
@ -992,20 +965,17 @@ static ResultCode CreateEvent(Handle* out_handle, u32 reset_type) {
|
|||
}
|
||||
|
||||
/// Duplicates a kernel handle
|
||||
static ResultCode DuplicateHandle(Handle* out, Handle handle) {
|
||||
CASCADE_RESULT(
|
||||
*out,
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Duplicate(handle));
|
||||
ResultCode SVC::DuplicateHandle(Handle* out, Handle handle) {
|
||||
CASCADE_RESULT(*out, kernel.GetCurrentProcess()->handle_table.Duplicate(handle));
|
||||
LOG_TRACE(Kernel_SVC, "duplicated 0x{:08X} to 0x{:08X}", handle, *out);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Signals an event
|
||||
static ResultCode SignalEvent(Handle handle) {
|
||||
ResultCode SVC::SignalEvent(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle);
|
||||
|
||||
SharedPtr<Event> evt =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Event>(handle);
|
||||
SharedPtr<Event> evt = kernel.GetCurrentProcess()->handle_table.Get<Event>(handle);
|
||||
if (evt == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -1015,11 +985,10 @@ static ResultCode SignalEvent(Handle handle) {
|
|||
}
|
||||
|
||||
/// Clears an event
|
||||
static ResultCode ClearEvent(Handle handle) {
|
||||
ResultCode SVC::ClearEvent(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle);
|
||||
|
||||
SharedPtr<Event> evt =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Event>(handle);
|
||||
SharedPtr<Event> evt = kernel.GetCurrentProcess()->handle_table.Get<Event>(handle);
|
||||
if (evt == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -1028,10 +997,9 @@ static ResultCode ClearEvent(Handle handle) {
|
|||
}
|
||||
|
||||
/// Creates a timer
|
||||
static ResultCode CreateTimer(Handle* out_handle, u32 reset_type) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) {
|
||||
SharedPtr<Timer> timer = kernel.CreateTimer(
|
||||
static_cast<ResetType>(reset_type), fmt ::format("timer-{:08x}", Core::CPU().GetReg(14)));
|
||||
static_cast<ResetType>(reset_type), fmt ::format("timer-{:08x}", system.CPU().GetReg(14)));
|
||||
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(timer)));
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
|
||||
|
@ -1040,11 +1008,10 @@ static ResultCode CreateTimer(Handle* out_handle, u32 reset_type) {
|
|||
}
|
||||
|
||||
/// Clears a timer
|
||||
static ResultCode ClearTimer(Handle handle) {
|
||||
ResultCode SVC::ClearTimer(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle);
|
||||
|
||||
SharedPtr<Timer> timer =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Timer>(handle);
|
||||
SharedPtr<Timer> timer = kernel.GetCurrentProcess()->handle_table.Get<Timer>(handle);
|
||||
if (timer == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -1053,15 +1020,14 @@ static ResultCode ClearTimer(Handle handle) {
|
|||
}
|
||||
|
||||
/// Starts a timer
|
||||
static ResultCode SetTimer(Handle handle, s64 initial, s64 interval) {
|
||||
ResultCode SVC::SetTimer(Handle handle, s64 initial, s64 interval) {
|
||||
LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle);
|
||||
|
||||
if (initial < 0 || interval < 0) {
|
||||
return ERR_OUT_OF_RANGE_KERNEL;
|
||||
}
|
||||
|
||||
SharedPtr<Timer> timer =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Timer>(handle);
|
||||
SharedPtr<Timer> timer = kernel.GetCurrentProcess()->handle_table.Get<Timer>(handle);
|
||||
if (timer == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -1071,11 +1037,10 @@ static ResultCode SetTimer(Handle handle, s64 initial, s64 interval) {
|
|||
}
|
||||
|
||||
/// Cancels a timer
|
||||
static ResultCode CancelTimer(Handle handle) {
|
||||
ResultCode SVC::CancelTimer(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle);
|
||||
|
||||
SharedPtr<Timer> timer =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Timer>(handle);
|
||||
SharedPtr<Timer> timer = kernel.GetCurrentProcess()->handle_table.Get<Timer>(handle);
|
||||
if (timer == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -1085,10 +1050,9 @@ static ResultCode CancelTimer(Handle handle) {
|
|||
}
|
||||
|
||||
/// Sleep the current thread
|
||||
static void SleepThread(s64 nanoseconds) {
|
||||
void SVC::SleepThread(s64 nanoseconds) {
|
||||
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ThreadManager& thread_manager = kernel.GetThreadManager();
|
||||
|
||||
// Don't attempt to yield execution if there are no available threads to run,
|
||||
|
@ -1102,20 +1066,20 @@ static void SleepThread(s64 nanoseconds) {
|
|||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
thread_manager.GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// This returns the total CPU ticks elapsed since the CPU was powered-on
|
||||
static s64 GetSystemTick() {
|
||||
s64 result = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
s64 SVC::GetSystemTick() {
|
||||
s64 result = system.CoreTiming().GetTicks();
|
||||
// Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end.
|
||||
// Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
|
||||
Core::System::GetInstance().CoreTiming().AddTicks(150);
|
||||
system.CoreTiming().AddTicks(150);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Creates a memory block at the specified address with the specified permissions and size
|
||||
static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my_permission,
|
||||
ResultCode SVC::CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my_permission,
|
||||
u32 other_permission) {
|
||||
if (size % Memory::PAGE_SIZE != 0)
|
||||
return ERR_MISALIGNED_SIZE;
|
||||
|
@ -1148,7 +1112,7 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32
|
|||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
|
||||
// When trying to create a memory block with address = 0,
|
||||
// if the process has the Shared Device Memory flag in the exheader,
|
||||
|
@ -1158,7 +1122,7 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32
|
|||
if (addr == 0 && current_process->flags.shared_device_mem)
|
||||
region = current_process->flags.memory_region;
|
||||
|
||||
shared_memory = Core::System::GetInstance().Kernel().CreateSharedMemory(
|
||||
shared_memory = kernel.CreateSharedMemory(
|
||||
current_process.get(), size, static_cast<MemoryPermission>(my_permission),
|
||||
static_cast<MemoryPermission>(other_permission), addr, region);
|
||||
CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(shared_memory)));
|
||||
|
@ -1167,12 +1131,11 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode CreatePort(Handle* server_port, Handle* client_port, VAddr name_address,
|
||||
ResultCode SVC::CreatePort(Handle* server_port, Handle* client_port, VAddr name_address,
|
||||
u32 max_sessions) {
|
||||
// TODO(Subv): Implement named ports.
|
||||
ASSERT_MSG(name_address == 0, "Named ports are currently unimplemented");
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
|
||||
auto ports = kernel.CreatePortPair(max_sessions);
|
||||
|
@ -1187,8 +1150,8 @@ static ResultCode CreatePort(Handle* server_port, Handle* client_port, VAddr nam
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) {
|
||||
SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
ResultCode SVC::CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) {
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
SharedPtr<ClientPort> client_port =
|
||||
current_process->handle_table.Get<ClientPort>(client_port_handle);
|
||||
if (client_port == nullptr)
|
||||
|
@ -1199,8 +1162,7 @@ static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode CreateSession(Handle* server_session, Handle* client_session) {
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
ResultCode SVC::CreateSession(Handle* server_session, Handle* client_session) {
|
||||
auto sessions = kernel.CreateSessionPair();
|
||||
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
|
@ -1215,8 +1177,8 @@ static ResultCode CreateSession(Handle* server_session, Handle* client_session)
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle) {
|
||||
SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess();
|
||||
ResultCode SVC::AcceptSession(Handle* out_server_session, Handle server_port_handle) {
|
||||
SharedPtr<Process> current_process = kernel.GetCurrentProcess();
|
||||
SharedPtr<ServerPort> server_port =
|
||||
current_process->handle_table.Get<ServerPort>(server_port_handle);
|
||||
if (server_port == nullptr)
|
||||
|
@ -1227,11 +1189,9 @@ static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_h
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) {
|
||||
ResultCode SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
|
||||
LOG_TRACE(Kernel_SVC, "called type={} param={}", type, param);
|
||||
|
||||
KernelSystem& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
switch ((SystemInfoType)type) {
|
||||
case SystemInfoType::REGION_MEMORY_USAGE:
|
||||
switch ((SystemInfoMemUsageRegion)param) {
|
||||
|
@ -1272,12 +1232,11 @@ static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) {
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) {
|
||||
ResultCode SVC::GetProcessInfo(s64* out, Handle process_handle, u32 type) {
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} type={}", process_handle, type);
|
||||
|
||||
SharedPtr<Process> process =
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Process>(
|
||||
process_handle);
|
||||
kernel.GetCurrentProcess()->handle_table.Get<Process>(process_handle);
|
||||
if (process == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -1319,79 +1278,69 @@ static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) {
|
|||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct FunctionDef {
|
||||
using Func = void();
|
||||
|
||||
u32 id;
|
||||
Func* func;
|
||||
const char* name;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static const FunctionDef SVC_Table[] = {
|
||||
const SVC::FunctionDef SVC::SVC_Table[] = {
|
||||
{0x00, nullptr, "Unknown"},
|
||||
{0x01, HLE::Wrap<ControlMemory>, "ControlMemory"},
|
||||
{0x02, HLE::Wrap<QueryMemory>, "QueryMemory"},
|
||||
{0x03, ExitProcess, "ExitProcess"},
|
||||
{0x01, &SVC::Wrap<&SVC::ControlMemory>, "ControlMemory"},
|
||||
{0x02, &SVC::Wrap<&SVC::QueryMemory>, "QueryMemory"},
|
||||
{0x03, &SVC::ExitProcess, "ExitProcess"},
|
||||
{0x04, nullptr, "GetProcessAffinityMask"},
|
||||
{0x05, nullptr, "SetProcessAffinityMask"},
|
||||
{0x06, nullptr, "GetProcessIdealProcessor"},
|
||||
{0x07, nullptr, "SetProcessIdealProcessor"},
|
||||
{0x08, HLE::Wrap<CreateThread>, "CreateThread"},
|
||||
{0x09, ExitThread, "ExitThread"},
|
||||
{0x0A, HLE::Wrap<SleepThread>, "SleepThread"},
|
||||
{0x0B, HLE::Wrap<GetThreadPriority>, "GetThreadPriority"},
|
||||
{0x0C, HLE::Wrap<SetThreadPriority>, "SetThreadPriority"},
|
||||
{0x08, &SVC::Wrap<&SVC::CreateThread>, "CreateThread"},
|
||||
{0x09, &SVC::ExitThread, "ExitThread"},
|
||||
{0x0A, &SVC::Wrap<&SVC::SleepThread>, "SleepThread"},
|
||||
{0x0B, &SVC::Wrap<&SVC::GetThreadPriority>, "GetThreadPriority"},
|
||||
{0x0C, &SVC::Wrap<&SVC::SetThreadPriority>, "SetThreadPriority"},
|
||||
{0x0D, nullptr, "GetThreadAffinityMask"},
|
||||
{0x0E, nullptr, "SetThreadAffinityMask"},
|
||||
{0x0F, nullptr, "GetThreadIdealProcessor"},
|
||||
{0x10, nullptr, "SetThreadIdealProcessor"},
|
||||
{0x11, nullptr, "GetCurrentProcessorNumber"},
|
||||
{0x12, nullptr, "Run"},
|
||||
{0x13, HLE::Wrap<CreateMutex>, "CreateMutex"},
|
||||
{0x14, HLE::Wrap<ReleaseMutex>, "ReleaseMutex"},
|
||||
{0x15, HLE::Wrap<CreateSemaphore>, "CreateSemaphore"},
|
||||
{0x16, HLE::Wrap<ReleaseSemaphore>, "ReleaseSemaphore"},
|
||||
{0x17, HLE::Wrap<CreateEvent>, "CreateEvent"},
|
||||
{0x18, HLE::Wrap<SignalEvent>, "SignalEvent"},
|
||||
{0x19, HLE::Wrap<ClearEvent>, "ClearEvent"},
|
||||
{0x1A, HLE::Wrap<CreateTimer>, "CreateTimer"},
|
||||
{0x1B, HLE::Wrap<SetTimer>, "SetTimer"},
|
||||
{0x1C, HLE::Wrap<CancelTimer>, "CancelTimer"},
|
||||
{0x1D, HLE::Wrap<ClearTimer>, "ClearTimer"},
|
||||
{0x1E, HLE::Wrap<CreateMemoryBlock>, "CreateMemoryBlock"},
|
||||
{0x1F, HLE::Wrap<MapMemoryBlock>, "MapMemoryBlock"},
|
||||
{0x20, HLE::Wrap<UnmapMemoryBlock>, "UnmapMemoryBlock"},
|
||||
{0x21, HLE::Wrap<CreateAddressArbiter>, "CreateAddressArbiter"},
|
||||
{0x22, HLE::Wrap<ArbitrateAddress>, "ArbitrateAddress"},
|
||||
{0x23, HLE::Wrap<CloseHandle>, "CloseHandle"},
|
||||
{0x24, HLE::Wrap<WaitSynchronization1>, "WaitSynchronization1"},
|
||||
{0x25, HLE::Wrap<WaitSynchronizationN>, "WaitSynchronizationN"},
|
||||
{0x13, &SVC::Wrap<&SVC::CreateMutex>, "CreateMutex"},
|
||||
{0x14, &SVC::Wrap<&SVC::ReleaseMutex>, "ReleaseMutex"},
|
||||
{0x15, &SVC::Wrap<&SVC::CreateSemaphore>, "CreateSemaphore"},
|
||||
{0x16, &SVC::Wrap<&SVC::ReleaseSemaphore>, "ReleaseSemaphore"},
|
||||
{0x17, &SVC::Wrap<&SVC::CreateEvent>, "CreateEvent"},
|
||||
{0x18, &SVC::Wrap<&SVC::SignalEvent>, "SignalEvent"},
|
||||
{0x19, &SVC::Wrap<&SVC::ClearEvent>, "ClearEvent"},
|
||||
{0x1A, &SVC::Wrap<&SVC::CreateTimer>, "CreateTimer"},
|
||||
{0x1B, &SVC::Wrap<&SVC::SetTimer>, "SetTimer"},
|
||||
{0x1C, &SVC::Wrap<&SVC::CancelTimer>, "CancelTimer"},
|
||||
{0x1D, &SVC::Wrap<&SVC::ClearTimer>, "ClearTimer"},
|
||||
{0x1E, &SVC::Wrap<&SVC::CreateMemoryBlock>, "CreateMemoryBlock"},
|
||||
{0x1F, &SVC::Wrap<&SVC::MapMemoryBlock>, "MapMemoryBlock"},
|
||||
{0x20, &SVC::Wrap<&SVC::UnmapMemoryBlock>, "UnmapMemoryBlock"},
|
||||
{0x21, &SVC::Wrap<&SVC::CreateAddressArbiter>, "CreateAddressArbiter"},
|
||||
{0x22, &SVC::Wrap<&SVC::ArbitrateAddress>, "ArbitrateAddress"},
|
||||
{0x23, &SVC::Wrap<&SVC::CloseHandle>, "CloseHandle"},
|
||||
{0x24, &SVC::Wrap<&SVC::WaitSynchronization1>, "WaitSynchronization1"},
|
||||
{0x25, &SVC::Wrap<&SVC::WaitSynchronizationN>, "WaitSynchronizationN"},
|
||||
{0x26, nullptr, "SignalAndWait"},
|
||||
{0x27, HLE::Wrap<DuplicateHandle>, "DuplicateHandle"},
|
||||
{0x28, HLE::Wrap<GetSystemTick>, "GetSystemTick"},
|
||||
{0x27, &SVC::Wrap<&SVC::DuplicateHandle>, "DuplicateHandle"},
|
||||
{0x28, &SVC::Wrap<&SVC::GetSystemTick>, "GetSystemTick"},
|
||||
{0x29, nullptr, "GetHandleInfo"},
|
||||
{0x2A, HLE::Wrap<GetSystemInfo>, "GetSystemInfo"},
|
||||
{0x2B, HLE::Wrap<GetProcessInfo>, "GetProcessInfo"},
|
||||
{0x2A, &SVC::Wrap<&SVC::GetSystemInfo>, "GetSystemInfo"},
|
||||
{0x2B, &SVC::Wrap<&SVC::GetProcessInfo>, "GetProcessInfo"},
|
||||
{0x2C, nullptr, "GetThreadInfo"},
|
||||
{0x2D, HLE::Wrap<ConnectToPort>, "ConnectToPort"},
|
||||
{0x2D, &SVC::Wrap<&SVC::ConnectToPort>, "ConnectToPort"},
|
||||
{0x2E, nullptr, "SendSyncRequest1"},
|
||||
{0x2F, nullptr, "SendSyncRequest2"},
|
||||
{0x30, nullptr, "SendSyncRequest3"},
|
||||
{0x31, nullptr, "SendSyncRequest4"},
|
||||
{0x32, HLE::Wrap<SendSyncRequest>, "SendSyncRequest"},
|
||||
{0x32, &SVC::Wrap<&SVC::SendSyncRequest>, "SendSyncRequest"},
|
||||
{0x33, nullptr, "OpenProcess"},
|
||||
{0x34, nullptr, "OpenThread"},
|
||||
{0x35, HLE::Wrap<GetProcessId>, "GetProcessId"},
|
||||
{0x36, HLE::Wrap<GetProcessIdOfThread>, "GetProcessIdOfThread"},
|
||||
{0x37, HLE::Wrap<GetThreadId>, "GetThreadId"},
|
||||
{0x38, HLE::Wrap<GetResourceLimit>, "GetResourceLimit"},
|
||||
{0x39, HLE::Wrap<GetResourceLimitLimitValues>, "GetResourceLimitLimitValues"},
|
||||
{0x3A, HLE::Wrap<GetResourceLimitCurrentValues>, "GetResourceLimitCurrentValues"},
|
||||
{0x35, &SVC::Wrap<&SVC::GetProcessId>, "GetProcessId"},
|
||||
{0x36, &SVC::Wrap<&SVC::GetProcessIdOfThread>, "GetProcessIdOfThread"},
|
||||
{0x37, &SVC::Wrap<&SVC::GetThreadId>, "GetThreadId"},
|
||||
{0x38, &SVC::Wrap<&SVC::GetResourceLimit>, "GetResourceLimit"},
|
||||
{0x39, &SVC::Wrap<&SVC::GetResourceLimitLimitValues>, "GetResourceLimitLimitValues"},
|
||||
{0x3A, &SVC::Wrap<&SVC::GetResourceLimitCurrentValues>, "GetResourceLimitCurrentValues"},
|
||||
{0x3B, nullptr, "GetThreadContext"},
|
||||
{0x3C, HLE::Wrap<Break>, "Break"},
|
||||
{0x3D, HLE::Wrap<OutputDebugString>, "OutputDebugString"},
|
||||
{0x3C, &SVC::Wrap<&SVC::Break>, "Break"},
|
||||
{0x3D, &SVC::Wrap<&SVC::OutputDebugString>, "OutputDebugString"},
|
||||
{0x3E, nullptr, "ControlPerformanceCounter"},
|
||||
{0x3F, nullptr, "Unknown"},
|
||||
{0x40, nullptr, "Unknown"},
|
||||
|
@ -1401,15 +1350,15 @@ static const FunctionDef SVC_Table[] = {
|
|||
{0x44, nullptr, "Unknown"},
|
||||
{0x45, nullptr, "Unknown"},
|
||||
{0x46, nullptr, "Unknown"},
|
||||
{0x47, HLE::Wrap<CreatePort>, "CreatePort"},
|
||||
{0x48, HLE::Wrap<CreateSessionToPort>, "CreateSessionToPort"},
|
||||
{0x49, HLE::Wrap<CreateSession>, "CreateSession"},
|
||||
{0x4A, HLE::Wrap<AcceptSession>, "AcceptSession"},
|
||||
{0x47, &SVC::Wrap<&SVC::CreatePort>, "CreatePort"},
|
||||
{0x48, &SVC::Wrap<&SVC::CreateSessionToPort>, "CreateSessionToPort"},
|
||||
{0x49, &SVC::Wrap<&SVC::CreateSession>, "CreateSession"},
|
||||
{0x4A, &SVC::Wrap<&SVC::AcceptSession>, "AcceptSession"},
|
||||
{0x4B, nullptr, "ReplyAndReceive1"},
|
||||
{0x4C, nullptr, "ReplyAndReceive2"},
|
||||
{0x4D, nullptr, "ReplyAndReceive3"},
|
||||
{0x4E, nullptr, "ReplyAndReceive4"},
|
||||
{0x4F, HLE::Wrap<ReplyAndReceive>, "ReplyAndReceive"},
|
||||
{0x4F, &SVC::Wrap<&SVC::ReplyAndReceive>, "ReplyAndReceive"},
|
||||
{0x50, nullptr, "BindInterrupt"},
|
||||
{0x51, nullptr, "UnbindInterrupt"},
|
||||
{0x52, nullptr, "InvalidateProcessDataCache"},
|
||||
|
@ -1455,10 +1404,10 @@ static const FunctionDef SVC_Table[] = {
|
|||
{0x7A, nullptr, "AddCodeSegment"},
|
||||
{0x7B, nullptr, "Backdoor"},
|
||||
{0x7C, nullptr, "KernelSetState"},
|
||||
{0x7D, HLE::Wrap<QueryProcessMemory>, "QueryProcessMemory"},
|
||||
{0x7D, &SVC::Wrap<&SVC::QueryProcessMemory>, "QueryProcessMemory"},
|
||||
};
|
||||
|
||||
static const FunctionDef* GetSVCInfo(u32 func_num) {
|
||||
const SVC::FunctionDef* SVC::GetSVCInfo(u32 func_num) {
|
||||
if (func_num >= ARRAY_SIZE(SVC_Table)) {
|
||||
LOG_ERROR(Kernel_SVC, "unknown svc=0x{:02X}", func_num);
|
||||
return nullptr;
|
||||
|
@ -1468,24 +1417,33 @@ static const FunctionDef* GetSVCInfo(u32 func_num) {
|
|||
|
||||
MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
|
||||
|
||||
void CallSVC(u32 immediate) {
|
||||
void SVC::CallSVC(u32 immediate) {
|
||||
MICROPROFILE_SCOPE(Kernel_SVC);
|
||||
|
||||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
DEBUG_ASSERT_MSG(Core::System::GetInstance().Kernel().GetCurrentProcess()->status ==
|
||||
ProcessStatus::Running,
|
||||
DEBUG_ASSERT_MSG(kernel.GetCurrentProcess()->status == ProcessStatus::Running,
|
||||
"Running threads from exiting processes is unimplemented");
|
||||
|
||||
const FunctionDef* info = GetSVCInfo(immediate);
|
||||
if (info) {
|
||||
if (info->func) {
|
||||
info->func();
|
||||
(this->*(info->func))();
|
||||
} else {
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented SVC function {}(..)", info->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SVC::SVC(Core::System& system) : system(system), kernel(system.Kernel()) {}
|
||||
|
||||
u32 SVC::GetReg(std::size_t n) {
|
||||
return system.CPU().GetReg(static_cast<int>(n));
|
||||
}
|
||||
|
||||
void SVC::SetReg(std::size_t n, u32 value) {
|
||||
system.CPU().SetReg(static_cast<int>(n), value);
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
|
|
@ -5,9 +5,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/svc_wrapper.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelSystem;
|
||||
|
||||
struct MemoryInfo {
|
||||
u32 base_address;
|
||||
u32 size;
|
||||
|
@ -48,6 +59,91 @@ enum class SystemInfoMemUsageRegion {
|
|||
BASE = 3,
|
||||
};
|
||||
|
||||
class SVC : public SVCWrapper<SVC> {
|
||||
public:
|
||||
SVC(Core::System& system);
|
||||
void CallSVC(u32 immediate);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
Kernel::KernelSystem& kernel;
|
||||
|
||||
friend class SVCWrapper<SVC>;
|
||||
|
||||
// ARM interfaces
|
||||
|
||||
u32 GetReg(std::size_t n);
|
||||
void SetReg(std::size_t n, u32 value);
|
||||
|
||||
// SVC interfaces
|
||||
|
||||
ResultCode ControlMemory(u32* out_addr, u32 addr0, u32 addr1, u32 size, u32 operation,
|
||||
u32 permissions);
|
||||
void ExitProcess();
|
||||
ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions);
|
||||
ResultCode UnmapMemoryBlock(Handle handle, u32 addr);
|
||||
ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address);
|
||||
ResultCode SendSyncRequest(Handle handle);
|
||||
ResultCode CloseHandle(Handle handle);
|
||||
ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds);
|
||||
ResultCode WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count,
|
||||
bool wait_all, s64 nano_seconds);
|
||||
ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_count,
|
||||
Handle reply_target);
|
||||
ResultCode CreateAddressArbiter(Handle* out_handle);
|
||||
ResultCode ArbitrateAddress(Handle handle, u32 address, u32 type, u32 value, s64 nanoseconds);
|
||||
void Break(u8 break_reason);
|
||||
void OutputDebugString(VAddr address, s32 len);
|
||||
ResultCode GetResourceLimit(Handle* resource_limit, Handle process_handle);
|
||||
ResultCode GetResourceLimitCurrentValues(VAddr values, Handle resource_limit_handle,
|
||||
VAddr names, u32 name_count);
|
||||
ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limit_handle, VAddr names,
|
||||
u32 name_count);
|
||||
ResultCode CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr stack_top,
|
||||
u32 priority, s32 processor_id);
|
||||
void ExitThread();
|
||||
ResultCode GetThreadPriority(u32* priority, Handle handle);
|
||||
ResultCode SetThreadPriority(Handle handle, u32 priority);
|
||||
ResultCode CreateMutex(Handle* out_handle, u32 initial_locked);
|
||||
ResultCode ReleaseMutex(Handle handle);
|
||||
ResultCode GetProcessId(u32* process_id, Handle process_handle);
|
||||
ResultCode GetProcessIdOfThread(u32* process_id, Handle thread_handle);
|
||||
ResultCode GetThreadId(u32* thread_id, Handle handle);
|
||||
ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count);
|
||||
ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count);
|
||||
ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info,
|
||||
Handle process_handle, u32 addr);
|
||||
ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 addr);
|
||||
ResultCode CreateEvent(Handle* out_handle, u32 reset_type);
|
||||
ResultCode DuplicateHandle(Handle* out, Handle handle);
|
||||
ResultCode SignalEvent(Handle handle);
|
||||
ResultCode ClearEvent(Handle handle);
|
||||
ResultCode CreateTimer(Handle* out_handle, u32 reset_type);
|
||||
ResultCode ClearTimer(Handle handle);
|
||||
ResultCode SetTimer(Handle handle, s64 initial, s64 interval);
|
||||
ResultCode CancelTimer(Handle handle);
|
||||
void SleepThread(s64 nanoseconds);
|
||||
s64 GetSystemTick();
|
||||
ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my_permission,
|
||||
u32 other_permission);
|
||||
ResultCode CreatePort(Handle* server_port, Handle* client_port, VAddr name_address,
|
||||
u32 max_sessions);
|
||||
ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle);
|
||||
ResultCode CreateSession(Handle* server_session, Handle* client_session);
|
||||
ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle);
|
||||
ResultCode GetSystemInfo(s64* out, u32 type, s32 param);
|
||||
ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type);
|
||||
|
||||
struct FunctionDef {
|
||||
using Func = void (SVC::*)();
|
||||
|
||||
u32 id;
|
||||
Func func;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
static const FunctionDef SVC_Table[];
|
||||
static const FunctionDef* GetSVCInfo(u32 func_num);
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
/*
|
||||
* This class defines the SVC ABI, and provides a wrapper template for translating between ARM
|
||||
* registers and SVC parameters and return value. The SVC context class should inherit from this
|
||||
* class using CRTP (`class SVC : public SVCWrapper<SVC> {...}`), and use the Wrap() template to
|
||||
* convert a SVC function interface to a void()-type function that interacts with registers
|
||||
* interface GetReg() and SetReg().
|
||||
*/
|
||||
template <typename Context>
|
||||
class SVCWrapper {
|
||||
protected:
|
||||
template <auto F>
|
||||
void Wrap() {
|
||||
WrapHelper<decltype(F)>::Call(*static_cast<Context*>(this), F);
|
||||
};
|
||||
|
||||
private:
|
||||
// A special param index represents the return value
|
||||
static constexpr std::size_t INDEX_RETURN = ~(std::size_t)0;
|
||||
|
||||
struct ParamSlot {
|
||||
// whether the slot is used for a param
|
||||
bool used;
|
||||
|
||||
// index of a param in the function signature, starting from 0, or special value
|
||||
// INDEX_RETURN
|
||||
std::size_t param_index;
|
||||
|
||||
// index of a 32-bit word within the param
|
||||
std::size_t word_index;
|
||||
};
|
||||
|
||||
// Size in words of the given type in ARM ABI
|
||||
template <typename T>
|
||||
static constexpr std::size_t WordSize() {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
if constexpr (std::is_pointer_v<T>) {
|
||||
return 1;
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return 1;
|
||||
} else {
|
||||
return (sizeof(T) - 1) / 4 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Size in words of the given type in ARM ABI with pointer removed
|
||||
template <typename T>
|
||||
static constexpr std::size_t OutputWordSize() {
|
||||
if constexpr (std::is_pointer_v<T>) {
|
||||
return WordSize<std::remove_pointer_t<T>>();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Describes the register assignments of a SVC
|
||||
struct SVCABI {
|
||||
static constexpr std::size_t RegCount = 8;
|
||||
|
||||
// Register assignments for input params.
|
||||
// For example, in[0] records which input param and word r0 stores
|
||||
std::array<ParamSlot, RegCount> in{};
|
||||
|
||||
// Register assignments for output params.
|
||||
// For example, out[0] records which output param (with pointer removed) and word r0 stores
|
||||
std::array<ParamSlot, RegCount> out{};
|
||||
|
||||
// Search the register assignments for a word of a param
|
||||
constexpr std::size_t GetRegisterIndex(std::size_t param_index,
|
||||
std::size_t word_index) const {
|
||||
for (std::size_t slot = 0; slot < RegCount; ++slot) {
|
||||
if (in[slot].used && in[slot].param_index == param_index &&
|
||||
in[slot].word_index == word_index) {
|
||||
return slot;
|
||||
}
|
||||
if (out[slot].used && out[slot].param_index == param_index &&
|
||||
out[slot].word_index == word_index) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
// should throw, but gcc bugs out here
|
||||
return 0x12345678;
|
||||
}
|
||||
};
|
||||
|
||||
// Generates ABI info for a given SVC signature R(Context::)(T...)
|
||||
template <typename R, typename... T>
|
||||
static constexpr SVCABI GetSVCABI() {
|
||||
constexpr std::size_t param_count = sizeof...(T);
|
||||
std::array<bool, param_count> param_is_output{{std::is_pointer_v<T>...}};
|
||||
std::array<std::size_t, param_count> param_size{{WordSize<T>()...}};
|
||||
std::array<std::size_t, param_count> output_param_size{{OutputWordSize<T>()...}};
|
||||
std::array<bool, param_count> param_need_align{
|
||||
{(std::is_same_v<T, u64> || std::is_same_v<T, s64>)...}};
|
||||
|
||||
// derives ARM ABI, which assigns all params to r0~r3 and then stack
|
||||
std::array<ParamSlot, 4> armabi_reg{};
|
||||
std::array<ParamSlot, 4> armabi_stack{};
|
||||
std::size_t reg_pos = 0;
|
||||
std::size_t stack_pos = 0;
|
||||
for (std::size_t i = 0; i < param_count; ++i) {
|
||||
if (param_need_align[i]) {
|
||||
reg_pos += reg_pos % 2;
|
||||
}
|
||||
for (std::size_t j = 0; j < param_size[i]; ++j) {
|
||||
if (reg_pos == 4) {
|
||||
armabi_stack[stack_pos++] = ParamSlot{true, i, j};
|
||||
} else {
|
||||
armabi_reg[reg_pos++] = ParamSlot{true, i, j};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now derives SVC ABI which is a modified version of ARM ABI
|
||||
|
||||
SVCABI mod_abi{};
|
||||
std::size_t out_pos = 0; // next available output register
|
||||
|
||||
// assign return value to output registers
|
||||
if constexpr (!std::is_void_v<R>) {
|
||||
for (std::size_t j = 0; j < WordSize<R>(); ++j) {
|
||||
mod_abi.out[out_pos++] = {true, INDEX_RETURN, j};
|
||||
}
|
||||
}
|
||||
|
||||
// assign output params (pointer-type params) to output registers
|
||||
for (std::size_t i = 0; i < param_count; ++i) {
|
||||
if (param_is_output[i]) {
|
||||
for (std::size_t j = 0; j < output_param_size[i]; ++j) {
|
||||
mod_abi.out[out_pos++] = ParamSlot{true, i, j};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assign input params (non-pointer-type params) to input registers
|
||||
stack_pos = 0; // next available stack param
|
||||
for (std::size_t k = 0; k < mod_abi.in.size(); ++k) {
|
||||
if (k < armabi_reg.size() && armabi_reg[k].used &&
|
||||
!param_is_output[armabi_reg[k].param_index]) {
|
||||
// If this is within the ARM ABI register range and it is a used input param, use
|
||||
// the same register position
|
||||
mod_abi.in[k] = armabi_reg[k];
|
||||
} else {
|
||||
// Otherwise, assign it with the next available stack input
|
||||
// If all stack inputs have been allocated, this would do nothing
|
||||
// and leaves the slot unused.
|
||||
// Loop until an input stack param is found
|
||||
while (stack_pos < armabi_stack.size() &&
|
||||
(!armabi_stack[stack_pos].used ||
|
||||
param_is_output[armabi_stack[stack_pos].param_index])) {
|
||||
++stack_pos;
|
||||
}
|
||||
// Reassign the stack param to the free register
|
||||
if (stack_pos < armabi_stack.size()) {
|
||||
mod_abi.in[k] = armabi_stack[stack_pos++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mod_abi;
|
||||
}
|
||||
|
||||
template <std::size_t param_index, std::size_t word_size, std::size_t... indices>
|
||||
static constexpr std::array<std::size_t, word_size> GetRegIndicesImpl(
|
||||
SVCABI abi, std::index_sequence<indices...>) {
|
||||
return {{abi.GetRegisterIndex(param_index, indices)...}};
|
||||
}
|
||||
|
||||
// Gets assigned register index for the param_index-th param of word_size in a function with
|
||||
// signature R(Context::)(Ts...)
|
||||
template <std::size_t param_index, std::size_t word_size, typename R, typename... Ts>
|
||||
static constexpr std::array<std::size_t, word_size> GetRegIndices() {
|
||||
constexpr SVCABI abi = GetSVCABI<R, Ts...>();
|
||||
return GetRegIndicesImpl<param_index, word_size>(abi,
|
||||
std::make_index_sequence<word_size>{});
|
||||
}
|
||||
|
||||
// Gets the value for the param_index-th param of word_size in a function with signature
|
||||
// R(Context::)(Ts...)
|
||||
// T is the param type, which must be a non-pointer as it is an input param.
|
||||
// Must hold: Ts[param_index] == T
|
||||
template <std::size_t param_index, typename T, typename R, typename... Ts>
|
||||
static void GetParam(Context& context, T& value) {
|
||||
static_assert(!std::is_pointer_v<T>, "T should not be a pointer");
|
||||
constexpr auto regi = GetRegIndices<param_index, WordSize<T>(), R, Ts...>();
|
||||
if constexpr (std::is_class_v<T> || std::is_union_v<T>) {
|
||||
std::array<u32, WordSize<T>()> buf;
|
||||
for (std::size_t i = 0; i < WordSize<T>(); ++i) {
|
||||
buf[i] = context.GetReg(regi[i]);
|
||||
}
|
||||
std::memcpy(&value, &buf, sizeof(T));
|
||||
} else if constexpr (WordSize<T>() == 2) {
|
||||
u64 l = context.GetReg(regi[0]);
|
||||
u64 h = context.GetReg(regi[1]);
|
||||
value = static_cast<T>(l | (h << 32));
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
// TODO: Is this correct or should only test the lowest byte?
|
||||
value = context.GetReg(regi[0]) != 0;
|
||||
} else {
|
||||
value = static_cast<T>(context.GetReg(regi[0]));
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the value for the param_index-th param of word_size in a function with signature
|
||||
// R(Context::)(Ts...)
|
||||
// T is the param type with pointer removed, which was originally a pointer-type output param
|
||||
// Must hold: (Ts[param_index] == T*) || (R==T && param_index == INDEX_RETURN)
|
||||
template <std::size_t param_index, typename T, typename R, typename... Ts>
|
||||
static void SetParam(Context& context, const T& value) {
|
||||
static_assert(!std::is_pointer_v<T>, "T should have pointer removed before passing in");
|
||||
constexpr auto regi = GetRegIndices<param_index, WordSize<T>(), R, Ts...>();
|
||||
if constexpr (std::is_class_v<T> || std::is_union_v<T>) {
|
||||
std::array<u32, WordSize<T>()> buf;
|
||||
std::memcpy(&buf, &value, sizeof(T));
|
||||
for (std::size_t i = 0; i < WordSize<T>(); ++i) {
|
||||
context.SetReg(regi[i], buf[i]);
|
||||
}
|
||||
} else if constexpr (WordSize<T>() == 2) {
|
||||
u64 u = static_cast<u64>(value);
|
||||
context.SetReg(regi[0], static_cast<u32>(u & 0xFFFFFFFF));
|
||||
context.SetReg(regi[1], static_cast<u32>(u >> 32));
|
||||
} else {
|
||||
u32 u = static_cast<u32>(value);
|
||||
context.SetReg(regi[0], u);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SVCT, typename R, typename... Ts>
|
||||
struct WrapPass;
|
||||
|
||||
template <typename SVCT, typename R, typename T, typename... Ts>
|
||||
struct WrapPass<SVCT, R, T, Ts...> {
|
||||
// Do I/O for the param T in function R(Context::svc)(Us..., T, Ts...) and then move on to
|
||||
// the next param.
|
||||
|
||||
// Us are params whose I/O is already handled.
|
||||
// T is the current param to do I/O.
|
||||
// Ts are params whose I/O is not handled yet.
|
||||
template <typename... Us>
|
||||
static void Call(Context& context, SVCT svc, Us... u) {
|
||||
static_assert(std::is_same_v<SVCT, R (Context::*)(Us..., T, Ts...)>);
|
||||
constexpr std::size_t current_param_index = sizeof...(Us);
|
||||
if constexpr (std::is_pointer_v<T>) {
|
||||
using OutputT = std::remove_pointer_t<T>;
|
||||
OutputT output;
|
||||
WrapPass<SVCT, R, Ts...>::Call(context, svc, u..., &output);
|
||||
SetParam<current_param_index, OutputT, R, Us..., T, Ts...>(context, output);
|
||||
} else {
|
||||
T input;
|
||||
GetParam<current_param_index, T, R, Us..., T, Ts...>(context, input);
|
||||
WrapPass<SVCT, R, Ts...>::Call(context, svc, u..., input);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename SVCT, typename R>
|
||||
struct WrapPass<SVCT, R /*empty for T, Ts...*/> {
|
||||
// Call function R(Context::svc)(Us...) and transfer the return value to registers
|
||||
template <typename... Us>
|
||||
static void Call(Context& context, SVCT svc, Us... u) {
|
||||
static_assert(std::is_same_v<SVCT, R (Context::*)(Us...)>);
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
(context.*svc)(u...);
|
||||
} else {
|
||||
R r = (context.*svc)(u...);
|
||||
SetParam<INDEX_RETURN, R, R, Us...>(context, r);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WrapHelper;
|
||||
|
||||
template <typename R, typename... T>
|
||||
struct WrapHelper<R (Context::*)(T...)> {
|
||||
static void Call(Context& context, R (Context::*svc)(T...)) {
|
||||
WrapPass<decltype(svc), R, T...>::Call(context, svc /*Empty for Us*/);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
Reference in New Issue