mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 03:51:48 +01:00
Add support for threads and mutexes
This commit adds support for threading and mutexes. However, there is also a basis of conditional variables but these don't work due to the lack of a shared memory model between the guest and host. So, conditional variables will be deferred to after the shared memory model is in place.
This commit is contained in:
parent
2aebf04b4b
commit
d02267c34f
@ -1,25 +1,37 @@
|
||||
#include "common.h"
|
||||
#include "nce.h"
|
||||
#include "gpu.h"
|
||||
#include <kernel/types/KThread.h>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
namespace skyline {
|
||||
void Mutex::lock() {
|
||||
while (flag.test_and_set(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
void Mutex::unlock() {
|
||||
flag.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
bool Mutex::try_lock() {
|
||||
return !flag.test_and_set(std::memory_order_acquire);
|
||||
while (true) {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
if (!flag.test_and_set(std::memory_order_acquire))
|
||||
return;
|
||||
asm volatile("yield");
|
||||
}
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupMutex::lock(Group group) {
|
||||
auto none = Group::None;
|
||||
while (!flag.compare_exchange_weak(none, group) && flag != group);
|
||||
num++;
|
||||
if (flag == group) {
|
||||
num++;
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
if (flag.compare_exchange_weak(none, group)) {
|
||||
num++;
|
||||
return;
|
||||
}
|
||||
asm volatile("yield");
|
||||
}
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupMutex::unlock() {
|
||||
@ -38,13 +50,16 @@ namespace skyline {
|
||||
stringMap[elem->FindAttribute("name")->Value()] = elem->GetText();
|
||||
break;
|
||||
case 'b':
|
||||
boolMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute("value")->BoolValue();
|
||||
boolMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute(
|
||||
"value")->BoolValue();
|
||||
break;
|
||||
case 'i':
|
||||
intMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute("value")->IntValue();
|
||||
intMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute(
|
||||
"value")->IntValue();
|
||||
break;
|
||||
default:
|
||||
syslog(LOG_ALERT, "Settings type is missing: %s for %s", elem->Value(), elem->FindAttribute("name")->Value());
|
||||
syslog(LOG_ALERT, "Settings type is missing: %s for %s", elem->Value(),
|
||||
elem->FindAttribute("name")->Value());
|
||||
break;
|
||||
};
|
||||
if (elem->NextSibling())
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <fstream>
|
||||
#include <syslog.h>
|
||||
#include <mutex>
|
||||
#import <thread>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
@ -24,7 +24,6 @@ namespace skyline {
|
||||
namespace constant {
|
||||
// Memory
|
||||
constexpr u64 BaseAddress = 0x8000000; //!< The address space base
|
||||
constexpr u64 BaseEnd = 0x7FFFFFFFFF; //!< The end of the address space
|
||||
constexpr u64 TotalPhyMem = 0xF8000000; // ~4 GB of RAM
|
||||
constexpr size_t DefStackSize = 0x1E8480; //!< The default amount of stack: 2 MB
|
||||
constexpr size_t HeapSizeDiv = 0x200000; //!< The amount heap size has to be divisible by
|
||||
@ -79,6 +78,7 @@ namespace skyline {
|
||||
constexpr u32 InvHandle = 0xE401; //!< "Invalid handle"
|
||||
constexpr u32 InvCombination = 0xE801; //!< "Invalid combination"
|
||||
constexpr u32 Timeout = 0xEA01; //!< "Timeout"
|
||||
constexpr u32 Interrupted = 0xEC01; //!< "Interrupted"
|
||||
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
|
||||
constexpr u32 NotFound = 0xF201; //!< "Not found"
|
||||
constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour"
|
||||
@ -135,9 +135,21 @@ namespace skyline {
|
||||
return value & ~multiple;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param address The address to check for alignment
|
||||
* @return If the address is page aligned
|
||||
*/
|
||||
inline bool PageAligned(u64 address) {
|
||||
return !(address & (PAGE_SIZE - 1U));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param address The address to check for alignment
|
||||
* @return If the address is word aligned
|
||||
*/
|
||||
inline bool WordAligned(u64 address) {
|
||||
return !(address & 3U);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,12 +168,16 @@ namespace skyline {
|
||||
* @brief Try to lock the mutex if it is unlocked else return
|
||||
* @return If the mutex was successfully locked or not
|
||||
*/
|
||||
bool try_lock();
|
||||
inline bool try_lock() {
|
||||
return !flag.test_and_set(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unlock the mutex if it is held by this thread
|
||||
*/
|
||||
void unlock();
|
||||
inline void unlock() {
|
||||
flag.clear(std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -121,7 +121,6 @@ namespace skyline::kernel::ipc {
|
||||
return static_cast<u16>(counter0_5) | static_cast<u16>(counter9_11) << 9;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorX) == 8);
|
||||
|
||||
/**
|
||||
@ -154,7 +153,6 @@ namespace skyline::kernel::ipc {
|
||||
return static_cast<u64>(size0_31) | static_cast<u64>(size32_35) << 32;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BufferDescriptorABW) == 12);
|
||||
|
||||
/**
|
||||
|
@ -91,7 +91,9 @@ namespace skyline::kernel {
|
||||
case memory::AddressSpaceType::AddressSpace32Bit:
|
||||
throw exception("32-bit address spaces are not supported");
|
||||
case memory::AddressSpaceType::AddressSpace36Bit: {
|
||||
code.address = 0x8000000;
|
||||
base.address = constant::BaseAddress;
|
||||
base.size = 0xFF8000000;
|
||||
code.address = base.address;
|
||||
code.size = 0x78000000;
|
||||
if(code.address > address || (code.size - (address - code.address)) < size)
|
||||
throw exception("Code mapping larger than 36-bit code region");
|
||||
@ -101,11 +103,13 @@ namespace skyline::kernel {
|
||||
stack.size = alias.size;
|
||||
heap.address = alias.address + alias.size;
|
||||
heap.size = 0x180000000;
|
||||
tlsIo.address = heap.address + heap.size;
|
||||
tlsIo.size = 0x1000000000;
|
||||
tlsIo.address = code.address;
|
||||
tlsIo.size = 0;
|
||||
break;
|
||||
}
|
||||
case memory::AddressSpaceType::AddressSpace39Bit: {
|
||||
base.address = constant::BaseAddress;
|
||||
base.size = 0x7FF8000000;
|
||||
code.address = utils::AlignDown(address, 0x200000);
|
||||
code.size = utils::AlignUp(address + size, 0x200000) - code.address;
|
||||
alias.address = code.address + code.size;
|
||||
@ -136,6 +140,8 @@ namespace skyline::kernel {
|
||||
|
||||
memory::Region MemoryManager::GetRegion(memory::Regions region) {
|
||||
switch(region) {
|
||||
case memory::Regions::Base:
|
||||
return base;
|
||||
case memory::Regions::Code:
|
||||
return code;
|
||||
case memory::Regions::Alias:
|
||||
|
@ -72,27 +72,19 @@ namespace skyline {
|
||||
u32 value;
|
||||
};
|
||||
|
||||
static_assert(sizeof(MemoryAttribute) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
|
||||
*/
|
||||
struct MemoryInfo {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u32 type;
|
||||
MemoryAttribute attributes;
|
||||
union {
|
||||
u32 _pad0_;
|
||||
struct {
|
||||
bool r : 1, w : 1, x : 1;
|
||||
};
|
||||
};
|
||||
u32 ipcRefCount;
|
||||
u32 deviceRefCount;
|
||||
u32 : 32;
|
||||
u64 address; //!< The base address of the mapping
|
||||
u64 size; //!< The size of the mapping
|
||||
u32 type; //!< The MemoryType of the mapping
|
||||
u32 attributes; //!< The attributes of the mapping
|
||||
u32 permissions; //!< The permissions of the mapping
|
||||
u32 ipcRefCount; //!< The IPC reference count (This is always 0)
|
||||
u32 deviceRefCount; //!< The device reference count (This is always 0)
|
||||
u32 _pad0_;
|
||||
};
|
||||
|
||||
static_assert(sizeof(MemoryInfo) == 0x28);
|
||||
|
||||
/**
|
||||
@ -153,7 +145,6 @@ namespace skyline {
|
||||
};
|
||||
u32 value;
|
||||
};
|
||||
|
||||
static_assert(sizeof(MemoryState) == sizeof(u32));
|
||||
|
||||
/**
|
||||
@ -187,6 +178,7 @@ namespace skyline {
|
||||
* @brief This enumerates all of the memory regions in the process address space
|
||||
*/
|
||||
enum class Regions {
|
||||
Base, //!< The region representing the entire address space
|
||||
Code, //!< The code region contains all of the loaded in code
|
||||
Alias, //!< The alias region is reserved for allocating thread stack before 2.0.0
|
||||
Heap, //!< The heap region is reserved for heap allocations
|
||||
@ -272,6 +264,7 @@ namespace skyline {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
std::forward_list<ChunkDescriptor> chunkList; //!< This linked list holds all the chunk descriptors
|
||||
memory::Region base{memory::Regions::Base}; //!< The Region object for the entire address space
|
||||
memory::Region code{memory::Regions::Code}; //!< The Region object for the code memory region
|
||||
memory::Region alias{memory::Regions::Alias}; //!< The Region object for the alias memory region
|
||||
memory::Region heap{memory::Regions::Heap}; //!< The Region object for the heap memory region
|
||||
|
@ -73,7 +73,7 @@ namespace skyline::kernel::svc {
|
||||
auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
|
||||
if(!stack.IsInside(destination)) {
|
||||
state.ctx->registers.w0 = constant::status::InvMemRange;
|
||||
state.logger->Warn("svcMapMemory: Addresses not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
auto descriptor = state.os->memory.Get(source);
|
||||
@ -87,16 +87,61 @@ namespace skyline::kernel::svc {
|
||||
state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, descriptor->chunk.state.value);
|
||||
return;
|
||||
}
|
||||
state.process->NewHandle<type::KPrivateMemory>(destination, size, memory::Permission{true, true, true}, memory::MemoryStates::Stack);
|
||||
state.process->NewHandle<type::KPrivateMemory>(destination, size, descriptor->block.permission, memory::MemoryStates::Stack);
|
||||
state.process->CopyMemory(source, destination, size);
|
||||
auto object = state.process->GetMemoryObject(source);
|
||||
if(!object)
|
||||
throw exception("svcMapMemory: Cannot find memory object in handle table for address 0x{:X}", source);
|
||||
object->UpdatePermission(source, size, {false, false, false});
|
||||
object->item->UpdatePermission(source, size, {false, false, false});
|
||||
state.logger->Debug("svcMapMemory: Mapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void UnmapMemory(DeviceState &state) {
|
||||
const u64 source = state.ctx->registers.x0;
|
||||
const u64 destination = state.ctx->registers.x1;
|
||||
const u64 size = state.ctx->registers.x2;
|
||||
if(!utils::PageAligned(destination) || !utils::PageAligned(source)) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
if(!utils::PageAligned(size)) {
|
||||
state.ctx->registers.w0 = constant::status::InvSize;
|
||||
state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
|
||||
return;
|
||||
}
|
||||
auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
|
||||
if(!stack.IsInside(source)) {
|
||||
state.ctx->registers.w0 = constant::status::InvMemRange;
|
||||
state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
auto sourceDesc = state.os->memory.Get(source);
|
||||
auto destDesc = state.os->memory.Get(destination);
|
||||
if(!sourceDesc || !destDesc) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
|
||||
return;
|
||||
}
|
||||
if(!destDesc->chunk.state.MapAllowed) {
|
||||
state.ctx->registers.w0 = constant::status::InvState;
|
||||
state.logger->Warn("svcUnmapMemory: Destination doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, destDesc->chunk.state.value);
|
||||
return;
|
||||
}
|
||||
auto destObject = state.process->GetMemoryObject(destination);
|
||||
if(!destObject)
|
||||
throw exception("svcUnmapMemory: Cannot find destination memory object in handle table for address 0x{:X}", destination);
|
||||
destObject->item->UpdatePermission(destination, size, sourceDesc->block.permission);
|
||||
state.process->CopyMemory(destination, source, size);
|
||||
auto sourceObject = state.process->GetMemoryObject(destination);
|
||||
if(!sourceObject)
|
||||
throw exception("svcUnmapMemory: Cannot find source memory object in handle table for address 0x{:X}", source);
|
||||
state.process->DeleteHandle(sourceObject->handle);
|
||||
state.logger->Debug("svcUnmapMemory: Unmapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void QueryMemory(DeviceState &state) {
|
||||
u64 address = state.ctx->registers.x2;
|
||||
memory::MemoryInfo memInfo{};
|
||||
@ -106,22 +151,22 @@ namespace skyline::kernel::svc {
|
||||
.address = descriptor->block.address,
|
||||
.size = descriptor->block.size,
|
||||
.type = static_cast<u32>(descriptor->chunk.state.type),
|
||||
.attributes = descriptor->block.attributes,
|
||||
.r = descriptor->block.permission.r,
|
||||
.w = descriptor->block.permission.w,
|
||||
.x = descriptor->block.permission.x,
|
||||
.attributes = descriptor->block.attributes.value,
|
||||
.permissions = static_cast<u32>(descriptor->block.permission.Get()),
|
||||
.deviceRefCount = 0,
|
||||
.ipcRefCount = 0,
|
||||
};
|
||||
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", memInfo.address, memInfo.size, memInfo.type, static_cast<bool>(descriptor->block.attributes.isUncached), descriptor->block.permission.r ? "R" : "-", descriptor->block.permission.w ? "W" : "-", descriptor->block.permission.x ? "X" : "-");
|
||||
} else {
|
||||
auto region = state.os->memory.GetRegion(memory::Regions::Base);
|
||||
auto baseEnd = region.address + region.size;
|
||||
memInfo = {
|
||||
.address = constant::BaseEnd,
|
||||
.size = ~(constant::BaseEnd - 1),
|
||||
.type = static_cast<u32>(memory::MemoryType::Unmapped)
|
||||
.address = region.address,
|
||||
.size = ~baseEnd + 1,
|
||||
.type = static_cast<u32>(memory::MemoryType::Unmapped),
|
||||
};
|
||||
state.logger->Debug("svcQueryMemory: Cannot find block of address: 0x{:X}", address);
|
||||
}
|
||||
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", memInfo.address, memInfo.size, memInfo.type, static_cast<bool>(memInfo.attributes.isUncached), memInfo.r ? "R" : "-", memInfo.w ? "W" : "-", memInfo.x ? "X" : "-");
|
||||
state.process->WriteMemory(memInfo, state.ctx->registers.x0);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
@ -153,6 +198,7 @@ namespace skyline::kernel::svc {
|
||||
auto thread = state.process->GetHandle<type::KThread>(handle);
|
||||
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->pid);
|
||||
thread->Start();
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
@ -160,7 +206,7 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
|
||||
void ExitThread(DeviceState &state) {
|
||||
state.logger->Debug("svcExitProcess: Exiting current thread: {}", state.thread->pid);
|
||||
state.logger->Debug("svcExitThread: Exiting current thread: {}", state.thread->pid);
|
||||
state.os->KillThread(state.thread->pid);
|
||||
}
|
||||
|
||||
@ -333,6 +379,11 @@ namespace skyline::kernel::svc {
|
||||
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
|
||||
auto start = utils::GetCurrTimeNs();
|
||||
while (true) {
|
||||
if(state.thread->cancelSync) {
|
||||
state.thread->cancelSync = false;
|
||||
state.ctx->registers.w0 = constant::status::Interrupted;
|
||||
break;
|
||||
}
|
||||
uint index{};
|
||||
for (const auto &object : objectTable) {
|
||||
if (object->signalled) {
|
||||
@ -351,82 +402,82 @@ namespace skyline::kernel::svc {
|
||||
}
|
||||
}
|
||||
|
||||
void CancelSynchronization(DeviceState &state) {
|
||||
try {
|
||||
state.process->GetHandle<type::KThread>(state.ctx->registers.w0)->cancelSync = true;
|
||||
} catch (const std::exception &) {
|
||||
state.logger->Warn("svcCancelSynchronization: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
|
||||
state.ctx->registers.w0 = constant::status::InvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void ArbitrateLock(DeviceState &state) {
|
||||
auto addr = state.ctx->registers.x1;
|
||||
if ((addr & ((1UL << WORD_BIT) - 1U))) {
|
||||
auto address = state.ctx->registers.x1;
|
||||
if (!utils::WordAligned(address)) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", addr);
|
||||
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", address);
|
||||
return;
|
||||
}
|
||||
auto handle = state.ctx->registers.w2;
|
||||
if (handle != state.thread->handle)
|
||||
throw exception("svcArbitrateLock: Called from another thread");
|
||||
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X} for thread 0x{:X}", addr, handle);
|
||||
state.process->MutexLock(addr);
|
||||
auto ownerHandle = state.ctx->registers.w0;
|
||||
auto requesterHandle = state.ctx->registers.w2;
|
||||
if (requesterHandle != state.thread->handle)
|
||||
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle);
|
||||
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X} for thread {}", address, state.thread->pid);
|
||||
state.process->MutexLock(address, ownerHandle);
|
||||
state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X} for thread {}", address, state.thread->pid);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
void ArbitrateUnlock(DeviceState &state) {
|
||||
auto address = state.ctx->registers.x0;
|
||||
if ((address & ((1UL << WORD_BIT) - 1U))) {
|
||||
if (!utils::WordAligned(address)) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address);
|
||||
return;
|
||||
}
|
||||
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address);
|
||||
state.process->MutexUnlock(address);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
if(state.process->MutexUnlock(address)) {
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", address);
|
||||
} else {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", address);
|
||||
}
|
||||
}
|
||||
|
||||
void WaitProcessWideKeyAtomic(DeviceState &state) {
|
||||
auto mtxAddress = state.ctx->registers.x0;
|
||||
if (!utils::WordAligned(mtxAddress)) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress);
|
||||
return;
|
||||
}
|
||||
auto condAddress = state.ctx->registers.x1;
|
||||
try {
|
||||
auto &cvar = state.process->condVars.at(condAddress);
|
||||
if ((mtxAddress & ((1UL << WORD_BIT) - 1U))) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress);
|
||||
return;
|
||||
}
|
||||
auto handle = state.ctx->registers.w2;
|
||||
if (handle != state.thread->handle)
|
||||
throw exception("svcWaitProcessWideKeyAtomic: Called from another thread");
|
||||
state.process->MutexLock(mtxAddress);
|
||||
auto &mutex = state.process->mutexes.at(mtxAddress);
|
||||
auto timeout = state.ctx->registers.x3;
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x:{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout);
|
||||
timespec spec{};
|
||||
clock_gettime(CLOCK_REALTIME, &spec);
|
||||
u128 time = u128(spec.tv_sec * 1000000000U + spec.tv_nsec) + timeout; // u128 to prevent overflow
|
||||
spec.tv_sec = static_cast<time_t>(time / 1000000000U);
|
||||
spec.tv_nsec = static_cast<long>(time % 1000000000U);
|
||||
if (pthread_cond_timedwait(&cvar, &mutex, &spec) == ETIMEDOUT)
|
||||
state.ctx->registers.w0 = constant::status::Timeout;
|
||||
else
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.process->MutexUnlock(mtxAddress);
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: No Conditional-Variable at 0x{:X}", condAddress);
|
||||
state.process->condVars[condAddress] = PTHREAD_COND_INITIALIZER;
|
||||
auto handle = state.ctx->registers.w2;
|
||||
if (handle != state.thread->handle)
|
||||
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle);
|
||||
if(!state.process->MutexUnlock(mtxAddress)) {
|
||||
state.ctx->registers.w0 = constant::status::InvAddress;
|
||||
state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mtxAddress);
|
||||
return;
|
||||
}
|
||||
auto timeout = state.ctx->registers.x3;
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout);
|
||||
if (state.process->ConditionalVariableWait(condAddress, timeout)) {
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
state.process->MutexLock(mtxAddress, handle, true);
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex");
|
||||
} else {
|
||||
state.ctx->registers.w0 = constant::status::Timeout;
|
||||
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
|
||||
}
|
||||
}
|
||||
|
||||
void SignalProcessWideKey(DeviceState &state) {
|
||||
auto address = state.ctx->registers.x0;
|
||||
auto count = state.ctx->registers.w1;
|
||||
try {
|
||||
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count);
|
||||
auto &cvar = state.process->condVars.at(address);
|
||||
if (count == UINT32_MAX)
|
||||
pthread_cond_broadcast(&cvar);
|
||||
else
|
||||
for (u32 iter = 0; iter < count; iter++)
|
||||
pthread_cond_signal(&cvar);
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Debug("svcSignalProcessWideKey: No Conditional-Variable at 0x{:X}", address);
|
||||
state.process->condVars[address] = PTHREAD_COND_INITIALIZER;
|
||||
}
|
||||
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count);
|
||||
state.process->ConditionalVariableSignal(address, count);
|
||||
state.ctx->registers.w0 = constant::status::Success;
|
||||
}
|
||||
|
||||
@ -516,10 +567,10 @@ namespace skyline::kernel::svc {
|
||||
out = state.process->heap->address + constant::DefStackSize + state.os->memory.GetProgramSize();
|
||||
break;
|
||||
case constant::infoState::AddressSpaceBaseAddr:
|
||||
out = constant::BaseAddress;
|
||||
out = state.os->memory.GetRegion(memory::Regions::Base).address;
|
||||
break;
|
||||
case constant::infoState::AddressSpaceSize:
|
||||
out = constant::BaseEnd;
|
||||
out = state.os->memory.GetRegion(memory::Regions::Base).size;
|
||||
break;
|
||||
case constant::infoState::StackRegionBaseAddr:
|
||||
out = state.os->memory.GetRegion(memory::Regions::Stack).address;
|
||||
|
@ -37,132 +37,142 @@ namespace skyline {
|
||||
};
|
||||
namespace kernel::svc {
|
||||
/**
|
||||
* @brief Sets the process heap to a given Size. It can both extend and shrink the heap. (https://switchbrew.org/wiki/SVC#svcSetHeapSize)
|
||||
* @brief Sets the process heap to a given Size. It can both extend and shrink the heap. (https://switchbrew.org/wiki/SVC#SetHeapSize)
|
||||
*/
|
||||
void SetHeapSize(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Change attribute of page-aligned memory region. This is used to turn on/off caching for a given memory area. (https://switchbrew.org/wiki/SVC#svcSetMemoryAttribute)
|
||||
* @brief Change attribute of page-aligned memory region. This is used to turn on/off caching for a given memory area. (https://switchbrew.org/wiki/SVC#SetMemoryAttribute)
|
||||
*/
|
||||
void SetMemoryAttribute(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Maps a memory range into a different range. Mainly used for adding guard pages around stack. (https://switchbrew.org/wiki/SVC#svcSetMemoryAttribute)
|
||||
* @brief Maps a memory range into a different range. Mainly used for adding guard pages around stack. (https://switchbrew.org/wiki/SVC#SetMemoryAttribute)
|
||||
*/
|
||||
void MapMemory(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Query information about an address (https://switchbrew.org/wiki/SVC#svcQueryMemory)
|
||||
* @brief Unmaps a region that was previously mapped with #MapMemory. (https://switchbrew.org/wiki/SVC#UnmapMemory)
|
||||
*/
|
||||
void UnmapMemory(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Query information about an address (https://switchbrew.org/wiki/SVC#QueryMemory)
|
||||
*/
|
||||
void QueryMemory(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Exits the current process (https://switchbrew.org/wiki/SVC#svcExitProcess)
|
||||
* @brief Exits the current process (https://switchbrew.org/wiki/SVC#ExitProcess)
|
||||
*/
|
||||
void ExitProcess(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Create a thread in the current process (https://switchbrew.org/wiki/SVC#svcCreateThread)
|
||||
* @brief Create a thread in the current process (https://switchbrew.org/wiki/SVC#CreateThread)
|
||||
*/
|
||||
void CreateThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#svcStartThread)
|
||||
* @brief Starts the thread for the provided handle (https://switchbrew.org/wiki/SVC#StartThread)
|
||||
*/
|
||||
void StartThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Exits the current thread (https://switchbrew.org/wiki/SVC#svcExitThread)
|
||||
* @brief Exits the current thread (https://switchbrew.org/wiki/SVC#ExitThread)
|
||||
*/
|
||||
void ExitThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Sleep for a specified amount of time, or yield thread (https://switchbrew.org/wiki/SVC#svcExitThread)
|
||||
* @brief Sleep for a specified amount of time, or yield thread (https://switchbrew.org/wiki/SVC#SleepThread)
|
||||
*/
|
||||
void SleepThread(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcGetThreadPriority)
|
||||
* @brief Get priority of provided thread handle (https://switchbrew.org/wiki/SVC#GetThreadPriority)
|
||||
*/
|
||||
void GetThreadPriority(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#svcSetThreadPriority)
|
||||
* @brief Set priority of provided thread handle (https://switchbrew.org/wiki/SVC#SetThreadPriority)
|
||||
*/
|
||||
void SetThreadPriority(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Maps the block supplied by the handle (https://switchbrew.org/wiki/SVC#svcMapSharedMemory)
|
||||
* @brief Maps the block supplied by the handle (https://switchbrew.org/wiki/SVC#MapSharedMemory)
|
||||
*/
|
||||
void MapSharedMemory(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to a KSharedMemory object (https://switchbrew.org/wiki/SVC#svcCreateTransferMemory)
|
||||
* @brief Returns a handle to a KSharedMemory object (https://switchbrew.org/wiki/SVC#CreateTransferMemory)
|
||||
*/
|
||||
void CreateTransferMemory(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Closes the specified handle
|
||||
* @brief Closes the specified handle (https://switchbrew.org/wiki/SVC#CloseHandle)
|
||||
*/
|
||||
void CloseHandle(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This resets a particular KEvent or KProcess which is signalled (https://switchbrew.org/wiki/SVC#svcResetSignal)
|
||||
* @brief This resets a particular KEvent or KProcess which is signalled (https://switchbrew.org/wiki/SVC#ResetSignal)
|
||||
*/
|
||||
void ResetSignal(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended (https://switchbrew.org/wiki/SVC#svcWaitSynchronization)
|
||||
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended (https://switchbrew.org/wiki/SVC#WaitSynchronization)
|
||||
*/
|
||||
void WaitSynchronization(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Locks a specified mutex
|
||||
* @brief If the referenced thread is currently in a synchronization call, that call will be interrupted (https://switchbrew.org/wiki/SVC#CancelSynchronization)
|
||||
*/
|
||||
void CancelSynchronization(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Locks a specified mutex (https://switchbrew.org/wiki/SVC#ArbitrateLock)
|
||||
*/
|
||||
void ArbitrateLock(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Unlocks a specified mutex
|
||||
* @brief Unlocks a specified mutex (https://switchbrew.org/wiki/SVC#ArbitrateUnlock)
|
||||
*/
|
||||
void ArbitrateUnlock(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Waits on a process-wide key (Conditional-Variable)
|
||||
* @brief Waits on a process-wide key (Conditional-Variable) (https://switchbrew.org/wiki/SVC#WaitProcessWideKeyAtomic)
|
||||
*/
|
||||
void WaitProcessWideKeyAtomic(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Signals a process-wide key (Conditional-Variable)
|
||||
* @brief Signals a process-wide key (Conditional-Variable) (https://switchbrew.org/wiki/SVC#SignalProcessWideKey)
|
||||
*/
|
||||
void SignalProcessWideKey(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This returns the value of CNTPCT_EL0 on the Switch (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
|
||||
* @brief This returns the value of CNTPCT_EL0 on the Switch (https://switchbrew.org/wiki/SVC#GetSystemTick)
|
||||
*/
|
||||
void GetSystemTick(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Connects to a named IPC port
|
||||
* @brief Connects to a named IPC port (https://switchbrew.org/wiki/SVC#ConnectToNamedPort)
|
||||
*/
|
||||
void ConnectToNamedPort(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Send a synchronous IPC request to a service
|
||||
* @brief Send a synchronous IPC request to a service (https://switchbrew.org/wiki/SVC#SendSyncRequest)
|
||||
*/
|
||||
void SendSyncRequest(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the PID of a specific thread
|
||||
* @brief Retrieves the PID of a specific thread (https://switchbrew.org/wiki/SVC#GetThreadId)
|
||||
*/
|
||||
void GetThreadId(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Outputs a debug string
|
||||
* @brief Outputs a debug string (https://switchbrew.org/wiki/SVC#OutputDebugString)
|
||||
*/
|
||||
void OutputDebugString(DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Retrieves a piece of information (https://switchbrew.org/wiki/SVC#svcGetInfo)
|
||||
* @brief Retrieves a piece of information (https://switchbrew.org/wiki/SVC#GetInfo)
|
||||
*/
|
||||
void GetInfo(DeviceState &state);
|
||||
|
||||
@ -175,7 +185,7 @@ namespace skyline {
|
||||
nullptr, // 0x02
|
||||
SetMemoryAttribute, // 0x03
|
||||
MapMemory, // 0x04
|
||||
nullptr, // 0x05
|
||||
UnmapMemory, // 0x05
|
||||
QueryMemory, // 0x06
|
||||
ExitProcess, // 0x07
|
||||
CreateThread, // 0x08
|
||||
@ -195,7 +205,7 @@ namespace skyline {
|
||||
CloseHandle, // 0x16
|
||||
ResetSignal, // 0x17
|
||||
WaitSynchronization, // 0x18
|
||||
nullptr, // 0x19
|
||||
CancelSynchronization, // 0x19
|
||||
ArbitrateLock, // 0x1a
|
||||
ArbitrateUnlock, // 0x1b
|
||||
WaitProcessWideKeyAtomic, // 0x1c
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <asm/unistd.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KPrivateMemory::KPrivateMemory(const DeviceState &state, u64 address, size_t size, memory::Permission permission, const memory::MemoryState memState) : state(state), address(address), size(size), KMemory(state, KType::KPrivateMemory) {
|
||||
KPrivateMemory::KPrivateMemory(const DeviceState &state, u64 address, size_t size, memory::Permission permission, const memory::MemoryState memState) : address(address), size(size), KMemory(state, KType::KPrivateMemory) {
|
||||
Registers fregs{};
|
||||
fregs.x0 = address;
|
||||
fregs.x1 = size;
|
||||
|
@ -7,9 +7,6 @@ namespace skyline::kernel::type {
|
||||
* @brief KPrivateMemory is used to map memory local to the guest process
|
||||
*/
|
||||
class KPrivateMemory : public KMemory {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
u64 address{}; //!< The address of the allocated memory
|
||||
size_t size{}; //!< The size of the allocated memory
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/uio.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <nce/guest.h>
|
||||
|
||||
namespace skyline::kernel::type {
|
||||
KProcess::TlsPage::TlsPage(u64 address) : address(address) {}
|
||||
@ -30,10 +32,11 @@ namespace skyline::kernel::type {
|
||||
if (!tlsPage->Full())
|
||||
return tlsPage->ReserveSlot();
|
||||
u64 address;
|
||||
if(tlsPages.empty())
|
||||
address = state.os->memory.GetRegion(memory::Regions::TlsIo).address;
|
||||
else
|
||||
address = (*(tlsPages.end()-1))->address + PAGE_SIZE;
|
||||
if (tlsPages.empty()) {
|
||||
auto region = state.os->memory.GetRegion(memory::Regions::TlsIo);
|
||||
address = region.size ? region.address : 0;
|
||||
} else
|
||||
address = (*(tlsPages.end() - 1))->address + PAGE_SIZE;
|
||||
auto tlsMem = NewHandle<KPrivateMemory>(address, PAGE_SIZE, memory::Permission(true, true, false), memory::MemoryStates::ThreadLocal).item;
|
||||
tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
|
||||
auto &tlsPage = tlsPages.back();
|
||||
@ -61,37 +64,23 @@ namespace skyline::kernel::type {
|
||||
status = Status::Exiting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function executed by all child threads after cloning
|
||||
*/
|
||||
int ExecuteChild(void *) {
|
||||
asm volatile("BRK #0xFF"); // BRK #constant::brkRdy (So we know when the thread/process is ready)
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 CreateThreadFunc(u64 stackTop) {
|
||||
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stackTop), CLONE_THREAD | CLONE_SIGHAND | CLONE_PTRACE | CLONE_FS | CLONE_VM | CLONE_FILES | CLONE_IO, nullptr);
|
||||
return static_cast<u64>(pid);
|
||||
}
|
||||
|
||||
std::shared_ptr<KThread> KProcess::CreateThread(u64 entryPoint, u64 entryArg, u64 stackTop, u8 priority) {
|
||||
/*
|
||||
* Future Reference:
|
||||
* https://android.googlesource.com/platform/bionic/+/master/libc/bionic/clone.cpp
|
||||
* https://android.googlesource.com/platform/bionic/+/master/libc/arch-arm64/bionic/__bionic_clone.S
|
||||
auto size = (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
|
||||
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, size, memory::Permission{true, true, false}, memory::MemoryStates::Reserved);
|
||||
Registers fregs{};
|
||||
fregs.regs[0] = entryPoint;
|
||||
fregs.regs[1] = stackTop;
|
||||
fregs.x0 = CLONE_THREAD | CLONE_SIGHAND | CLONE_PTRACE | CLONE_FS | CLONE_VM | CLONE_FILES | CLONE_IO;
|
||||
fregs.x1 = stackTop;
|
||||
fregs.x3 = tlsMem->Map(0, size, memory::Permission{true, true, false});
|
||||
fregs.x8 = __NR_clone;
|
||||
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs);
|
||||
auto pid = static_cast<pid_t>(fregs.regs[0]);
|
||||
if (pid == -1)
|
||||
fregs.x5 = reinterpret_cast<u64>(&guest::entry);
|
||||
fregs.x6 = entryPoint;
|
||||
state.nce->ExecuteFunction(ThreadCall::Clone, fregs);
|
||||
if (static_cast<int>(fregs.x0) < 0)
|
||||
throw exception("Cannot create thread: Address: 0x{:X}, Stack Top: 0x{:X}", entryPoint, stackTop);
|
||||
auto process = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this).item;
|
||||
auto pid = static_cast<pid_t>(fregs.x0);
|
||||
auto process = NewHandle<KThread>(pid, entryPoint, entryArg, stackTop, GetTlsSlot(), priority, this, tlsMem).item;
|
||||
threads[pid] = process;
|
||||
return process;
|
||||
*/
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
|
||||
@ -136,46 +125,107 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<KMemory> KProcess::GetMemoryObject(u64 address) {
|
||||
for(auto& [handle, object] : state.process->handles) {
|
||||
switch(object->objectType) {
|
||||
std::optional<KProcess::HandleOut<KMemory>> KProcess::GetMemoryObject(u64 address) {
|
||||
for (auto&[handle, object] : state.process->handles) {
|
||||
switch (object->objectType) {
|
||||
case type::KType::KPrivateMemory:
|
||||
case type::KType::KSharedMemory:
|
||||
case type::KType::KTransferMemory: {
|
||||
auto mem = std::static_pointer_cast<type::KMemory>(object);
|
||||
if (mem->IsInside(address))
|
||||
return mem;
|
||||
return std::optional<KProcess::HandleOut<KMemory>>({mem, handle});
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void KProcess::MutexLock(u64 address) {
|
||||
try {
|
||||
auto mtx = mutexes.at(address);
|
||||
pthread_mutex_lock(&mtx);
|
||||
u32 mtxVal = ReadMemory<u32>(address);
|
||||
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | state.thread->handle;
|
||||
WriteMemory(mtxVal, address);
|
||||
} catch (const std::out_of_range &) {
|
||||
mutexes[address] = PTHREAD_MUTEX_INITIALIZER;
|
||||
void KProcess::MutexLock(u64 address, handle_t owner, bool alwaysLock) {
|
||||
std::unique_lock lock(mutexLock);
|
||||
u32 mtxVal = ReadMemory<u32>(address);
|
||||
if(alwaysLock) {
|
||||
if(!mtxVal) {
|
||||
state.logger->Warn("Mutex Value was 0");
|
||||
mtxVal = (constant::MtxOwnerMask & state.thread->handle);
|
||||
WriteMemory<u32>(mtxVal, address);
|
||||
return;
|
||||
// TODO: Replace with atomic CAS
|
||||
}
|
||||
} else {
|
||||
if (mtxVal != (owner | ~constant::MtxOwnerMask))
|
||||
return;
|
||||
}
|
||||
auto &mtxWaiters = mutexes[address];
|
||||
std::shared_ptr<WaitStatus> status;
|
||||
for (auto it = mtxWaiters.begin();;++it) {
|
||||
if (it != mtxWaiters.end() && (*it)->priority >= state.thread->priority)
|
||||
continue;
|
||||
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->pid);
|
||||
mtxWaiters.insert(it, status);
|
||||
break;
|
||||
}
|
||||
lock.unlock();
|
||||
while (!status->flag);
|
||||
lock.lock();
|
||||
for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it)
|
||||
if((*it)->pid == state.thread->pid) {
|
||||
mtxWaiters.erase(it);
|
||||
break;
|
||||
}
|
||||
mtxVal = (constant::MtxOwnerMask & state.thread->handle) | (mtxWaiters.empty() ? 0 : ~constant::MtxOwnerMask);
|
||||
WriteMemory<u32>(mtxVal, address);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void KProcess::MutexUnlock(u64 address) {
|
||||
try {
|
||||
auto mtx = mutexes.at(address);
|
||||
u32 mtxVal = ReadMemory<u32>(address);
|
||||
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
|
||||
throw exception("A non-owner thread tried to release a mutex");
|
||||
bool KProcess::MutexUnlock(u64 address) {
|
||||
std::lock_guard lock(mutexLock);
|
||||
u32 mtxVal = ReadMemory<u32>(address);
|
||||
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
|
||||
return false;
|
||||
auto &mtxWaiters = mutexes[address];
|
||||
if (mtxWaiters.empty()) {
|
||||
mtxVal = 0;
|
||||
WriteMemory(mtxVal, address);
|
||||
pthread_mutex_unlock(&mtx);
|
||||
} catch (const std::out_of_range &) {
|
||||
mutexes[address] = PTHREAD_MUTEX_INITIALIZER;
|
||||
WriteMemory<u32>(mtxVal, address);
|
||||
} else
|
||||
(*mtxWaiters.begin())->flag = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KProcess::ConditionalVariableWait(u64 address, u64 timeout) {
|
||||
std::unique_lock lock(conditionalLock);
|
||||
auto &condWaiters = conditionals[address];
|
||||
std::shared_ptr<WaitStatus> status;
|
||||
for (auto it = condWaiters.begin();;++it) {
|
||||
if (it != condWaiters.end() && (*it)->priority >= state.thread->priority)
|
||||
continue;
|
||||
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->pid);
|
||||
condWaiters.insert(it, status);
|
||||
break;
|
||||
}
|
||||
lock.unlock();
|
||||
bool timedOut{};
|
||||
auto start = utils::GetCurrTimeNs();
|
||||
while (!status->flag) {
|
||||
if ((utils::GetCurrTimeNs() - start) >= timeout)
|
||||
timedOut = true;
|
||||
}
|
||||
lock.lock();
|
||||
for (auto it = condWaiters.begin(); it != condWaiters.end(); ++it)
|
||||
if((*it)->pid == state.thread->pid) {
|
||||
condWaiters.erase(it);
|
||||
break;
|
||||
}
|
||||
lock.unlock();
|
||||
return !timedOut;
|
||||
}
|
||||
|
||||
void KProcess::ConditionalVariableSignal(u64 address, u64 amount) {
|
||||
std::lock_guard lock(conditionalLock);
|
||||
auto &condWaiters = conditionals[address];
|
||||
amount = std::min(condWaiters.size(), amount);
|
||||
for (size_t i = 0; i < amount; ++i)
|
||||
condWaiters[i]->flag = true;
|
||||
}
|
||||
}
|
||||
|
@ -85,15 +85,28 @@ namespace skyline::kernel::type {
|
||||
Exiting //!< The process is exiting
|
||||
} status = Status::Created; //!< The state of the process
|
||||
|
||||
/**
|
||||
* @brief This is used to hold information about a single waiting thread for mutexes and conditional variables
|
||||
*/
|
||||
struct WaitStatus {
|
||||
std::atomic_bool flag{false}; //!< The underlying atomic flag of the thread
|
||||
u8 priority; //!< The priority of the thread
|
||||
pid_t pid; //!< The PID of the thread
|
||||
|
||||
WaitStatus(u8 priority, pid_t pid) : priority(priority), pid(pid) {}
|
||||
};
|
||||
|
||||
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
|
||||
pid_t pid; //!< The PID of the main thread
|
||||
int memFd; //!< The file descriptor to the memory of the process
|
||||
std::unordered_map<handle_t, std::shared_ptr<KObject>> handles; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
|
||||
std::unordered_map<pid_t, std::shared_ptr<KThread>> threads; //!< A mapping from a PID to it's corresponding KThread object
|
||||
std::unordered_map<u64, pthread_mutex_t> mutexes; //!< A map from a mutex's address to a vector of threads waiting on it
|
||||
std::unordered_map<u64, pthread_cond_t> condVars; //!< A map from a conditional variable's address to a vector of threads waiting on it
|
||||
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it
|
||||
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it
|
||||
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
|
||||
std::shared_ptr<KPrivateMemory> heap; //!< The kernel memory object backing the allocated heap
|
||||
Mutex mutexLock; //!< This Mutex is to prevent concurrent mutex operations to happen at once
|
||||
Mutex conditionalLock; //!< This Mutex is to prevent concurrent conditional variable operations to happen at once
|
||||
|
||||
/**
|
||||
* @brief Creates a KThread object for the main thread and opens the process's memory file
|
||||
@ -145,6 +158,17 @@ namespace skyline::kernel::type {
|
||||
WriteMemory(&item, address, sizeof(Type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes an object to process memory
|
||||
* @tparam Type The type of the object to be written
|
||||
* @param item The object to write
|
||||
* @param address The address of the object
|
||||
*/
|
||||
template<typename Type>
|
||||
inline void WriteMemory(const Type &item, u64 address) const {
|
||||
WriteMemory(&item, address, sizeof(Type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read data from the process's memory
|
||||
* @param destination The address to the location where the process memory is written
|
||||
@ -238,7 +262,7 @@ namespace skyline::kernel::type {
|
||||
* @param address The address to look for
|
||||
* @return A shared pointer to the corresponding KMemory object
|
||||
*/
|
||||
std::shared_ptr<KMemory> GetMemoryObject(u64 address);
|
||||
std::optional<HandleOut<KMemory>> GetMemoryObject(u64 address);
|
||||
|
||||
/**
|
||||
* @brief This deletes a certain handle from the handle table
|
||||
@ -251,14 +275,31 @@ namespace skyline::kernel::type {
|
||||
/**
|
||||
* @brief This locks the Mutex at the specified address
|
||||
* @param address The address of the mutex
|
||||
* @param owner The handle of the current mutex owner
|
||||
* @param alwaysLock If to return rather than lock if owner tag is not matched
|
||||
*/
|
||||
void MutexLock(u64 address);
|
||||
void MutexLock(u64 address, handle_t owner, bool alwaysLock = false);
|
||||
|
||||
/**
|
||||
* @brief This unlocks the Mutex at the specified address
|
||||
* @param address The address of the mutex
|
||||
* @return If the mutex was successfully unlocked
|
||||
*/
|
||||
void MutexUnlock(u64 address);
|
||||
bool MutexUnlock(u64 address);
|
||||
|
||||
/**
|
||||
* @param address The address of the conditional variable
|
||||
* @param timeout The amount of time to wait for the conditional variable
|
||||
* @return If the conditional variable was successfully waited for or timed out
|
||||
*/
|
||||
bool ConditionalVariableWait(u64 address, u64 timeout);
|
||||
|
||||
/**
|
||||
* @brief This signals a number of conditional variable waiters
|
||||
* @param address The address of the conditional variable
|
||||
* @param amount The amount of waiters to signal
|
||||
*/
|
||||
void ConditionalVariableSignal(u64 address, u64 amount);
|
||||
|
||||
/**
|
||||
* @brief This resets the object to an unsignalled state
|
||||
|
@ -19,12 +19,13 @@ namespace skyline::kernel::type {
|
||||
Running, //!< The thread is running currently
|
||||
Dead //!< The thread is dead and not running
|
||||
} status = Status::Created; //!< The state of the thread
|
||||
std::atomic<bool> cancelSync; //!< This is to flag to a thread to cancel a synchronization call it currently is in
|
||||
std::shared_ptr<type::KSharedMemory> ctxMemory; //!< The KSharedMemory of the shared memory allocated by the guest process TLS
|
||||
handle_t handle; // The handle of the object in the handle table
|
||||
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
|
||||
u64 stackTop; //!< The top of the stack (Where it starts growing downwards from)
|
||||
u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread
|
||||
u8 priority; //!< Hold the priority of a thread in Nintendo format
|
||||
u8 priority; //!< The priority of a thread in Nintendo format
|
||||
|
||||
/**
|
||||
* @param state The state of the device
|
||||
@ -55,11 +56,6 @@ namespace skyline::kernel::type {
|
||||
*/
|
||||
void Kill();
|
||||
|
||||
/**
|
||||
* @brief This wakes up the thread from it's sleep (no-op if thread is already awake)
|
||||
*/
|
||||
void WakeUp();
|
||||
|
||||
/**
|
||||
* @brief Update the priority level for the process.
|
||||
* @details Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority]. We rescale the priority from Nintendo scale to that of Android.
|
||||
|
@ -38,7 +38,7 @@ namespace skyline {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
} catch (const std::exception &e) {
|
||||
state.logger->Error(e.what());
|
||||
} catch (...) {
|
||||
state.logger->Error("An unknown exception has occurred");
|
||||
@ -109,7 +109,7 @@ namespace skyline {
|
||||
ctx->registers.x0 = entryArg;
|
||||
ctx->registers.x1 = handle;
|
||||
ctx->state = ThreadState::WaitRun;
|
||||
state.logger->Debug("Starting thread with PID: {}", thread->pid);
|
||||
state.logger->Debug("Starting kernel thread for guest thread: {}", thread->pid);
|
||||
threadMap[thread->pid] = std::make_shared<std::thread>(&NCE::KernelThread, this, thread->pid);
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ namespace skyline::guest {
|
||||
"MOV LR, SP\n\t"
|
||||
"SVC #0\n\t"
|
||||
"MOV SP, LR\n\t"
|
||||
"LDR LR, [SP], #16" :: : "x0", "x1", "x2", "x3", "x4", "x5", "x8");
|
||||
"LDR LR, [SP], #16" ::: "x0", "x1", "x2", "x3", "x4", "x5", "x8");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -179,6 +179,50 @@ namespace skyline::guest {
|
||||
auto end = src + size;
|
||||
while (src < end)
|
||||
*(src++) = *(dest++);
|
||||
} else if (ctx->commandId == static_cast<u32>(ThreadCall::Clone)) {
|
||||
saveCtxStack();
|
||||
loadCtxTls();
|
||||
asm("STR LR, [SP, #-16]!\n\t"
|
||||
"MOV LR, SP\n\t"
|
||||
"SVC #0\n\t"
|
||||
"CBNZ X0, .parent\n\t"
|
||||
"MSR TPIDR_EL0, X3\n\t"
|
||||
"MOV LR, 0\n\t"
|
||||
"MOV X0, X6\n\t"
|
||||
"MOV X1, XZR\n\t"
|
||||
"MOV X2, XZR\n\t"
|
||||
"MOV X3, XZR\n\t"
|
||||
"MOV X4, XZR\n\t"
|
||||
"MOV X6, XZR\n\t"
|
||||
"MOV X7, XZR\n\t"
|
||||
"MOV X8, XZR\n\t"
|
||||
"MOV X9, XZR\n\t"
|
||||
"MOV X10, XZR\n\t"
|
||||
"MOV X11, XZR\n\t"
|
||||
"MOV X12, XZR\n\t"
|
||||
"MOV X13, XZR\n\t"
|
||||
"MOV X14, XZR\n\t"
|
||||
"MOV X15, XZR\n\t"
|
||||
"MOV X16, XZR\n\t"
|
||||
"MOV X17, XZR\n\t"
|
||||
"MOV X18, XZR\n\t"
|
||||
"MOV X19, XZR\n\t"
|
||||
"MOV X20, XZR\n\t"
|
||||
"MOV X21, XZR\n\t"
|
||||
"MOV X22, XZR\n\t"
|
||||
"MOV X23, XZR\n\t"
|
||||
"MOV X24, XZR\n\t"
|
||||
"MOV X25, XZR\n\t"
|
||||
"MOV X26, XZR\n\t"
|
||||
"MOV X27, XZR\n\t"
|
||||
"MOV X28, XZR\n\t"
|
||||
"MOV X29, XZR\n\t"
|
||||
"BR X5\n\t"
|
||||
".parent:\n\t"
|
||||
"MOV SP, LR\n\t"
|
||||
"LDR LR, [SP], #16");
|
||||
saveCtxTls();
|
||||
loadCtxStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,7 +312,7 @@ namespace skyline::guest {
|
||||
"MOV X27, XZR\n\t"
|
||||
"MOV X28, XZR\n\t"
|
||||
"MOV X29, XZR\n\t"
|
||||
"RET"::"r"(address), "r"(ctx->registers.x0), "r"(ctx->registers.x1) : "x0", "x1", "lr");
|
||||
"RET" :: "r"(address), "r"(ctx->registers.x0), "r"(ctx->registers.x1) : "x0", "x1", "lr");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ namespace skyline {
|
||||
constexpr size_t saveCtxSize = 20 * sizeof(u32);
|
||||
constexpr size_t loadCtxSize = 20 * sizeof(u32);
|
||||
#ifdef NDEBUG
|
||||
constexpr size_t svcHandlerSize = 175 * sizeof(u32);
|
||||
constexpr size_t svcHandlerSize = 225 * sizeof(u32);
|
||||
#else
|
||||
constexpr size_t svcHandlerSize = 275 * sizeof(u32);
|
||||
constexpr size_t svcHandlerSize = 400 * sizeof(u32);
|
||||
#endif
|
||||
|
||||
void entry(u64 address);
|
||||
|
@ -136,6 +136,7 @@ namespace skyline {
|
||||
enum class ThreadCall : u32 {
|
||||
Syscall = 0x100, //!< A linux syscall needs to be called from the guest
|
||||
Memcopy = 0x101, //!< To copy memory from one location to another
|
||||
Clone = 0x102, //!< Use the clone syscall to create a new thread
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -26,14 +26,13 @@ namespace skyline::kernel {
|
||||
munmap(stack, stackSize);
|
||||
throw exception("Failed to create guard pages");
|
||||
}
|
||||
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission(true, true, false), memory::MemoryStates::Reserved);
|
||||
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission{true, true, false}, memory::MemoryStates::Reserved);
|
||||
tlsMem->guest = tlsMem->kernel;
|
||||
pid_t pid = clone(reinterpret_cast<int (*)(void *)>(&guest::entry), stack + stackSize, CLONE_FILES | CLONE_FS | CLONE_SETTLS | SIGCHLD, reinterpret_cast<void *>(entry), nullptr, reinterpret_cast<void *>(tlsMem->guest.address));
|
||||
if (pid == -1)
|
||||
throw exception("Call to clone() has failed: {}", strerror(errno));
|
||||
state.logger->Debug("Successfully created process with PID: {}", pid);
|
||||
process = std::make_shared<kernel::type::KProcess>(state, pid, argument, reinterpret_cast<u64>(stack), stackSize, tlsMem);
|
||||
state.logger->Debug("Successfully created process with PID: {}", pid);
|
||||
return process;
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
}
|
||||
thread(start = true) {
|
||||
val snackbar = Snackbar.make(findViewById(android.R.id.content), getString(R.string.searching_roms), Snackbar.LENGTH_INDEFINITE)
|
||||
runOnUiThread {snackbar.show()}
|
||||
try {
|
||||
runOnUiThread{adapter.clear()}
|
||||
val entries = findFile("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
||||
@ -102,6 +104,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
notifyUser(e.message!!)
|
||||
}
|
||||
}
|
||||
runOnUiThread {snackbar.dismiss()}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,4 +34,5 @@
|
||||
<string name="handheld_enabled">The system will emulate being in handheld mode</string>
|
||||
<string name="docked_enabled">The system will emulate being in docked mode</string>
|
||||
<string name="theme">Theme</string>
|
||||
<string name="searching_roms">Searching for ROMs</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user