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:
◱ PixelyIon 2020-02-01 21:21:32 +05:30 committed by ◱ PixelyIon
parent 2aebf04b4b
commit d02267c34f
19 changed files with 429 additions and 208 deletions

View File

@ -1,25 +1,37 @@
#include "common.h" #include "common.h"
#include "nce.h" #include "nce.h"
#include "gpu.h" #include "gpu.h"
#include <kernel/types/KThread.h>
#include <tinyxml2.h> #include <tinyxml2.h>
namespace skyline { namespace skyline {
void Mutex::lock() { void Mutex::lock() {
while (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))
void Mutex::unlock() { return;
flag.clear(std::memory_order_release); asm volatile("yield");
} }
sched_yield();
bool Mutex::try_lock() { }
return !flag.test_and_set(std::memory_order_acquire);
} }
void GroupMutex::lock(Group group) { void GroupMutex::lock(Group group) {
auto none = Group::None; auto none = Group::None;
while (!flag.compare_exchange_weak(none, group) && flag != group); if (flag == group) {
num++; 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() { void GroupMutex::unlock() {
@ -38,13 +50,16 @@ namespace skyline {
stringMap[elem->FindAttribute("name")->Value()] = elem->GetText(); stringMap[elem->FindAttribute("name")->Value()] = elem->GetText();
break; break;
case 'b': case 'b':
boolMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute("value")->BoolValue(); boolMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute(
"value")->BoolValue();
break; break;
case 'i': case 'i':
intMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute("value")->IntValue(); intMap[elem->FindAttribute("name")->Value()] = elem->FindAttribute(
"value")->IntValue();
break; break;
default: 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; break;
}; };
if (elem->NextSibling()) if (elem->NextSibling())

View File

@ -6,7 +6,7 @@
#include <fstream> #include <fstream>
#include <syslog.h> #include <syslog.h>
#include <mutex> #include <mutex>
#import <thread> #include <thread>
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <memory> #include <memory>
@ -24,7 +24,6 @@ namespace skyline {
namespace constant { namespace constant {
// Memory // Memory
constexpr u64 BaseAddress = 0x8000000; //!< The address space base 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 u64 TotalPhyMem = 0xF8000000; // ~4 GB of RAM
constexpr size_t DefStackSize = 0x1E8480; //!< The default amount of stack: 2 MB 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 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 InvHandle = 0xE401; //!< "Invalid handle"
constexpr u32 InvCombination = 0xE801; //!< "Invalid combination" constexpr u32 InvCombination = 0xE801; //!< "Invalid combination"
constexpr u32 Timeout = 0xEA01; //!< "Timeout" constexpr u32 Timeout = 0xEA01; //!< "Timeout"
constexpr u32 Interrupted = 0xEC01; //!< "Interrupted"
constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles" constexpr u32 MaxHandles = 0xEE01; //!< "Too many handles"
constexpr u32 NotFound = 0xF201; //!< "Not found" constexpr u32 NotFound = 0xF201; //!< "Not found"
constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour" constexpr u32 Unimpl = 0x177202; //!< "Unimplemented behaviour"
@ -135,9 +135,21 @@ namespace skyline {
return value & ~multiple; return value & ~multiple;
} }
/**
* @param address The address to check for alignment
* @return If the address is page aligned
*/
inline bool PageAligned(u64 address) { inline bool PageAligned(u64 address) {
return !(address & (PAGE_SIZE - 1U)); 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 * @brief Try to lock the mutex if it is unlocked else return
* @return If the mutex was successfully locked or not * @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 * @brief Unlock the mutex if it is held by this thread
*/ */
void unlock(); inline void unlock() {
flag.clear(std::memory_order_release);
}
}; };
/** /**

View File

@ -121,7 +121,6 @@ namespace skyline::kernel::ipc {
return static_cast<u16>(counter0_5) | static_cast<u16>(counter9_11) << 9; return static_cast<u16>(counter0_5) | static_cast<u16>(counter9_11) << 9;
} }
}; };
static_assert(sizeof(BufferDescriptorX) == 8); 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; return static_cast<u64>(size0_31) | static_cast<u64>(size32_35) << 32;
} }
}; };
static_assert(sizeof(BufferDescriptorABW) == 12); static_assert(sizeof(BufferDescriptorABW) == 12);
/** /**

View File

@ -91,7 +91,9 @@ namespace skyline::kernel {
case memory::AddressSpaceType::AddressSpace32Bit: case memory::AddressSpaceType::AddressSpace32Bit:
throw exception("32-bit address spaces are not supported"); throw exception("32-bit address spaces are not supported");
case memory::AddressSpaceType::AddressSpace36Bit: { case memory::AddressSpaceType::AddressSpace36Bit: {
code.address = 0x8000000; base.address = constant::BaseAddress;
base.size = 0xFF8000000;
code.address = base.address;
code.size = 0x78000000; code.size = 0x78000000;
if(code.address > address || (code.size - (address - code.address)) < size) if(code.address > address || (code.size - (address - code.address)) < size)
throw exception("Code mapping larger than 36-bit code region"); throw exception("Code mapping larger than 36-bit code region");
@ -101,11 +103,13 @@ namespace skyline::kernel {
stack.size = alias.size; stack.size = alias.size;
heap.address = alias.address + alias.size; heap.address = alias.address + alias.size;
heap.size = 0x180000000; heap.size = 0x180000000;
tlsIo.address = heap.address + heap.size; tlsIo.address = code.address;
tlsIo.size = 0x1000000000; tlsIo.size = 0;
break; break;
} }
case memory::AddressSpaceType::AddressSpace39Bit: { case memory::AddressSpaceType::AddressSpace39Bit: {
base.address = constant::BaseAddress;
base.size = 0x7FF8000000;
code.address = utils::AlignDown(address, 0x200000); code.address = utils::AlignDown(address, 0x200000);
code.size = utils::AlignUp(address + size, 0x200000) - code.address; code.size = utils::AlignUp(address + size, 0x200000) - code.address;
alias.address = code.address + code.size; alias.address = code.address + code.size;
@ -136,6 +140,8 @@ namespace skyline::kernel {
memory::Region MemoryManager::GetRegion(memory::Regions region) { memory::Region MemoryManager::GetRegion(memory::Regions region) {
switch(region) { switch(region) {
case memory::Regions::Base:
return base;
case memory::Regions::Code: case memory::Regions::Code:
return code; return code;
case memory::Regions::Alias: case memory::Regions::Alias:

View File

@ -72,27 +72,19 @@ namespace skyline {
u32 value; u32 value;
}; };
static_assert(sizeof(MemoryAttribute) == sizeof(u32));
/** /**
* @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo * @brief This contains information about a chunk of memory: https://switchbrew.org/wiki/SVC#MemoryInfo
*/ */
struct MemoryInfo { struct MemoryInfo {
u64 address; u64 address; //!< The base address of the mapping
u64 size; u64 size; //!< The size of the mapping
u32 type; u32 type; //!< The MemoryType of the mapping
MemoryAttribute attributes; u32 attributes; //!< The attributes of the mapping
union { u32 permissions; //!< The permissions of the mapping
u32 _pad0_; u32 ipcRefCount; //!< The IPC reference count (This is always 0)
struct { u32 deviceRefCount; //!< The device reference count (This is always 0)
bool r : 1, w : 1, x : 1; u32 _pad0_;
};
};
u32 ipcRefCount;
u32 deviceRefCount;
u32 : 32;
}; };
static_assert(sizeof(MemoryInfo) == 0x28); static_assert(sizeof(MemoryInfo) == 0x28);
/** /**
@ -153,7 +145,6 @@ namespace skyline {
}; };
u32 value; u32 value;
}; };
static_assert(sizeof(MemoryState) == sizeof(u32)); 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 * @brief This enumerates all of the memory regions in the process address space
*/ */
enum class Regions { enum class Regions {
Base, //!< The region representing the entire address space
Code, //!< The code region contains all of the loaded in code 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 Alias, //!< The alias region is reserved for allocating thread stack before 2.0.0
Heap, //!< The heap region is reserved for heap allocations Heap, //!< The heap region is reserved for heap allocations
@ -272,6 +264,7 @@ namespace skyline {
private: private:
const DeviceState &state; //!< The state of the device const DeviceState &state; //!< The state of the device
std::forward_list<ChunkDescriptor> chunkList; //!< This linked list holds all the chunk descriptors 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 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 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 memory::Region heap{memory::Regions::Heap}; //!< The Region object for the heap memory region

View File

@ -73,7 +73,7 @@ namespace skyline::kernel::svc {
auto stack = state.os->memory.GetRegion(memory::Regions::Stack); auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
if(!stack.IsInside(destination)) { if(!stack.IsInside(destination)) {
state.ctx->registers.w0 = constant::status::InvMemRange; 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; return;
} }
auto descriptor = state.os->memory.Get(source); 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); 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; 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); state.process->CopyMemory(source, destination, size);
auto object = state.process->GetMemoryObject(source); auto object = state.process->GetMemoryObject(source);
if(!object) if(!object)
throw exception("svcMapMemory: Cannot find memory object in handle table for address 0x{:X}", source); 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.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; 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) { void QueryMemory(DeviceState &state) {
u64 address = state.ctx->registers.x2; u64 address = state.ctx->registers.x2;
memory::MemoryInfo memInfo{}; memory::MemoryInfo memInfo{};
@ -106,22 +151,22 @@ namespace skyline::kernel::svc {
.address = descriptor->block.address, .address = descriptor->block.address,
.size = descriptor->block.size, .size = descriptor->block.size,
.type = static_cast<u32>(descriptor->chunk.state.type), .type = static_cast<u32>(descriptor->chunk.state.type),
.attributes = descriptor->block.attributes, .attributes = descriptor->block.attributes.value,
.r = descriptor->block.permission.r, .permissions = static_cast<u32>(descriptor->block.permission.Get()),
.w = descriptor->block.permission.w,
.x = descriptor->block.permission.x,
.deviceRefCount = 0, .deviceRefCount = 0,
.ipcRefCount = 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 { } else {
auto region = state.os->memory.GetRegion(memory::Regions::Base);
auto baseEnd = region.address + region.size;
memInfo = { memInfo = {
.address = constant::BaseEnd, .address = region.address,
.size = ~(constant::BaseEnd - 1), .size = ~baseEnd + 1,
.type = static_cast<u32>(memory::MemoryType::Unmapped) .type = static_cast<u32>(memory::MemoryType::Unmapped),
}; };
state.logger->Debug("svcQueryMemory: Cannot find block of address: 0x{:X}", address); 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.process->WriteMemory(memInfo, state.ctx->registers.x0);
state.ctx->registers.w0 = constant::status::Success; state.ctx->registers.w0 = constant::status::Success;
} }
@ -153,6 +198,7 @@ namespace skyline::kernel::svc {
auto thread = state.process->GetHandle<type::KThread>(handle); auto thread = state.process->GetHandle<type::KThread>(handle);
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->pid); state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->pid);
thread->Start(); thread->Start();
state.ctx->registers.w0 = constant::status::Success;
} catch (const std::exception &) { } catch (const std::exception &) {
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle); state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::InvHandle; state.ctx->registers.w0 = constant::status::InvHandle;
@ -160,7 +206,7 @@ namespace skyline::kernel::svc {
} }
void ExitThread(DeviceState &state) { 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); 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); state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
auto start = utils::GetCurrTimeNs(); auto start = utils::GetCurrTimeNs();
while (true) { while (true) {
if(state.thread->cancelSync) {
state.thread->cancelSync = false;
state.ctx->registers.w0 = constant::status::Interrupted;
break;
}
uint index{}; uint index{};
for (const auto &object : objectTable) { for (const auto &object : objectTable) {
if (object->signalled) { 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) { void ArbitrateLock(DeviceState &state) {
auto addr = state.ctx->registers.x1; auto address = state.ctx->registers.x1;
if ((addr & ((1UL << WORD_BIT) - 1U))) { if (!utils::WordAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress; 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; return;
} }
auto handle = state.ctx->registers.w2; auto ownerHandle = state.ctx->registers.w0;
if (handle != state.thread->handle) auto requesterHandle = state.ctx->registers.w2;
throw exception("svcArbitrateLock: Called from another thread"); if (requesterHandle != state.thread->handle)
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X} for thread 0x{:X}", addr, handle); throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle);
state.process->MutexLock(addr); 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; state.ctx->registers.w0 = constant::status::Success;
} }
void ArbitrateUnlock(DeviceState &state) { void ArbitrateUnlock(DeviceState &state) {
auto address = state.ctx->registers.x0; auto address = state.ctx->registers.x0;
if ((address & ((1UL << WORD_BIT) - 1U))) { if (!utils::WordAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress; state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address); state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address);
return; return;
} }
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address); state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address);
state.process->MutexUnlock(address); if(state.process->MutexUnlock(address)) {
state.ctx->registers.w0 = constant::status::Success; 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) { void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddress = state.ctx->registers.x0; 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; auto condAddress = state.ctx->registers.x1;
try { auto handle = state.ctx->registers.w2;
auto &cvar = state.process->condVars.at(condAddress); if (handle != state.thread->handle)
if ((mtxAddress & ((1UL << WORD_BIT) - 1U))) { throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle);
state.ctx->registers.w0 = constant::status::InvAddress; if(!state.process->MutexUnlock(mtxAddress)) {
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress); state.ctx->registers.w0 = constant::status::InvAddress;
return; state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mtxAddress);
} return;
auto handle = state.ctx->registers.w2; }
if (handle != state.thread->handle) auto timeout = state.ctx->registers.x3;
throw exception("svcWaitProcessWideKeyAtomic: Called from another thread"); state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout);
state.process->MutexLock(mtxAddress); if (state.process->ConditionalVariableWait(condAddress, timeout)) {
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;
state.ctx->registers.w0 = constant::status::Success; 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) { void SignalProcessWideKey(DeviceState &state) {
auto address = state.ctx->registers.x0; auto address = state.ctx->registers.x0;
auto count = state.ctx->registers.w1; auto count = state.ctx->registers.w1;
try { state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count);
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count); state.process->ConditionalVariableSignal(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.ctx->registers.w0 = constant::status::Success; 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(); out = state.process->heap->address + constant::DefStackSize + state.os->memory.GetProgramSize();
break; break;
case constant::infoState::AddressSpaceBaseAddr: case constant::infoState::AddressSpaceBaseAddr:
out = constant::BaseAddress; out = state.os->memory.GetRegion(memory::Regions::Base).address;
break; break;
case constant::infoState::AddressSpaceSize: case constant::infoState::AddressSpaceSize:
out = constant::BaseEnd; out = state.os->memory.GetRegion(memory::Regions::Base).size;
break; break;
case constant::infoState::StackRegionBaseAddr: case constant::infoState::StackRegionBaseAddr:
out = state.os->memory.GetRegion(memory::Regions::Stack).address; out = state.os->memory.GetRegion(memory::Regions::Stack).address;

View File

@ -37,132 +37,142 @@ namespace skyline {
}; };
namespace kernel::svc { 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); void CreateTransferMemory(DeviceState &state);
/** /**
* @brief Closes the specified handle * @brief Closes the specified handle (https://switchbrew.org/wiki/SVC#CloseHandle)
*/ */
void CloseHandle(DeviceState &state); 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); 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); 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); void ArbitrateLock(DeviceState &state);
/** /**
* @brief Unlocks a specified mutex * @brief Unlocks a specified mutex (https://switchbrew.org/wiki/SVC#ArbitrateUnlock)
*/ */
void ArbitrateUnlock(DeviceState &state); 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); 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); 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); 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); 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); 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); void GetThreadId(DeviceState &state);
/** /**
* @brief Outputs a debug string * @brief Outputs a debug string (https://switchbrew.org/wiki/SVC#OutputDebugString)
*/ */
void OutputDebugString(DeviceState &state); 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); void GetInfo(DeviceState &state);
@ -175,7 +185,7 @@ namespace skyline {
nullptr, // 0x02 nullptr, // 0x02
SetMemoryAttribute, // 0x03 SetMemoryAttribute, // 0x03
MapMemory, // 0x04 MapMemory, // 0x04
nullptr, // 0x05 UnmapMemory, // 0x05
QueryMemory, // 0x06 QueryMemory, // 0x06
ExitProcess, // 0x07 ExitProcess, // 0x07
CreateThread, // 0x08 CreateThread, // 0x08
@ -195,7 +205,7 @@ namespace skyline {
CloseHandle, // 0x16 CloseHandle, // 0x16
ResetSignal, // 0x17 ResetSignal, // 0x17
WaitSynchronization, // 0x18 WaitSynchronization, // 0x18
nullptr, // 0x19 CancelSynchronization, // 0x19
ArbitrateLock, // 0x1a ArbitrateLock, // 0x1a
ArbitrateUnlock, // 0x1b ArbitrateUnlock, // 0x1b
WaitProcessWideKeyAtomic, // 0x1c WaitProcessWideKeyAtomic, // 0x1c

View File

@ -4,7 +4,7 @@
#include <asm/unistd.h> #include <asm/unistd.h>
namespace skyline::kernel::type { 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{}; Registers fregs{};
fregs.x0 = address; fregs.x0 = address;
fregs.x1 = size; fregs.x1 = size;

View File

@ -7,9 +7,6 @@ namespace skyline::kernel::type {
* @brief KPrivateMemory is used to map memory local to the guest process * @brief KPrivateMemory is used to map memory local to the guest process
*/ */
class KPrivateMemory : public KMemory { class KPrivateMemory : public KMemory {
private:
const DeviceState &state; //!< The state of the device
public: public:
u64 address{}; //!< The address of the allocated memory u64 address{}; //!< The address of the allocated memory
size_t size{}; //!< The size of the allocated memory size_t size{}; //!< The size of the allocated memory

View File

@ -4,6 +4,8 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <sys/uio.h> #include <sys/uio.h>
#include <asm/unistd.h>
#include <nce/guest.h>
namespace skyline::kernel::type { namespace skyline::kernel::type {
KProcess::TlsPage::TlsPage(u64 address) : address(address) {} KProcess::TlsPage::TlsPage(u64 address) : address(address) {}
@ -30,10 +32,11 @@ namespace skyline::kernel::type {
if (!tlsPage->Full()) if (!tlsPage->Full())
return tlsPage->ReserveSlot(); return tlsPage->ReserveSlot();
u64 address; u64 address;
if(tlsPages.empty()) if (tlsPages.empty()) {
address = state.os->memory.GetRegion(memory::Regions::TlsIo).address; auto region = state.os->memory.GetRegion(memory::Regions::TlsIo);
else address = region.size ? region.address : 0;
address = (*(tlsPages.end()-1))->address + PAGE_SIZE; } else
address = (*(tlsPages.end() - 1))->address + PAGE_SIZE;
auto tlsMem = NewHandle<KPrivateMemory>(address, PAGE_SIZE, memory::Permission(true, true, false), memory::MemoryStates::ThreadLocal).item; 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)); tlsPages.push_back(std::make_shared<TlsPage>(tlsMem->address));
auto &tlsPage = tlsPages.back(); auto &tlsPage = tlsPages.back();
@ -61,37 +64,23 @@ namespace skyline::kernel::type {
status = Status::Exiting; 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) { std::shared_ptr<KThread> KProcess::CreateThread(u64 entryPoint, u64 entryArg, u64 stackTop, u8 priority) {
/* auto size = (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
* Future Reference: auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, size, memory::Permission{true, true, false}, memory::MemoryStates::Reserved);
* 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
Registers fregs{}; Registers fregs{};
fregs.regs[0] = entryPoint; fregs.x0 = CLONE_THREAD | CLONE_SIGHAND | CLONE_PTRACE | CLONE_FS | CLONE_VM | CLONE_FILES | CLONE_IO;
fregs.regs[1] = stackTop; fregs.x1 = stackTop;
fregs.x3 = tlsMem->Map(0, size, memory::Permission{true, true, false});
fregs.x8 = __NR_clone; fregs.x8 = __NR_clone;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs); fregs.x5 = reinterpret_cast<u64>(&guest::entry);
auto pid = static_cast<pid_t>(fregs.regs[0]); fregs.x6 = entryPoint;
if (pid == -1) 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); 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; threads[pid] = process;
return process; return process;
*/
return nullptr;
} }
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const { 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) { std::optional<KProcess::HandleOut<KMemory>> KProcess::GetMemoryObject(u64 address) {
for(auto& [handle, object] : state.process->handles) { for (auto&[handle, object] : state.process->handles) {
switch(object->objectType) { switch (object->objectType) {
case type::KType::KPrivateMemory: case type::KType::KPrivateMemory:
case type::KType::KSharedMemory: case type::KType::KSharedMemory:
case type::KType::KTransferMemory: { case type::KType::KTransferMemory: {
auto mem = std::static_pointer_cast<type::KMemory>(object); auto mem = std::static_pointer_cast<type::KMemory>(object);
if (mem->IsInside(address)) if (mem->IsInside(address))
return mem; return std::optional<KProcess::HandleOut<KMemory>>({mem, handle});
} }
default: default:
break; break;
} }
} }
return nullptr; return std::nullopt;
} }
void KProcess::MutexLock(u64 address) { void KProcess::MutexLock(u64 address, handle_t owner, bool alwaysLock) {
try { std::unique_lock lock(mutexLock);
auto mtx = mutexes.at(address); u32 mtxVal = ReadMemory<u32>(address);
pthread_mutex_lock(&mtx); if(alwaysLock) {
u32 mtxVal = ReadMemory<u32>(address); if(!mtxVal) {
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | state.thread->handle; state.logger->Warn("Mutex Value was 0");
WriteMemory(mtxVal, address); mtxVal = (constant::MtxOwnerMask & state.thread->handle);
} catch (const std::out_of_range &) { WriteMemory<u32>(mtxVal, address);
mutexes[address] = PTHREAD_MUTEX_INITIALIZER; 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) { bool KProcess::MutexUnlock(u64 address) {
try { std::lock_guard lock(mutexLock);
auto mtx = mutexes.at(address); u32 mtxVal = ReadMemory<u32>(address);
u32 mtxVal = ReadMemory<u32>(address); if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle) return false;
throw exception("A non-owner thread tried to release a mutex"); auto &mtxWaiters = mutexes[address];
if (mtxWaiters.empty()) {
mtxVal = 0; mtxVal = 0;
WriteMemory(mtxVal, address); WriteMemory<u32>(mtxVal, address);
pthread_mutex_unlock(&mtx); } else
} catch (const std::out_of_range &) { (*mtxWaiters.begin())->flag = true;
mutexes[address] = PTHREAD_MUTEX_INITIALIZER; 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;
} }
} }

View File

@ -85,15 +85,28 @@ namespace skyline::kernel::type {
Exiting //!< The process is exiting Exiting //!< The process is exiting
} status = Status::Created; //!< The state of the process } 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 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 pid_t pid; //!< The PID of the main thread
int memFd; //!< The file descriptor to the memory of the process 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<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<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, 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, 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>>> 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::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 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 * @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)); 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 * @brief Read data from the process's memory
* @param destination The address to the location where the process memory is written * @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 * @param address The address to look for
* @return A shared pointer to the corresponding KMemory object * @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 * @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 * @brief This locks the Mutex at the specified address
* @param address The address of the mutex * @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 * @brief This unlocks the Mutex at the specified address
* @param address The address of the mutex * @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 * @brief This resets the object to an unsignalled state

View File

@ -19,12 +19,13 @@ namespace skyline::kernel::type {
Running, //!< The thread is running currently Running, //!< The thread is running currently
Dead //!< The thread is dead and not running Dead //!< The thread is dead and not running
} status = Status::Created; //!< The state of the thread } 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 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 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]) 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 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 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 * @param state The state of the device
@ -55,11 +56,6 @@ namespace skyline::kernel::type {
*/ */
void Kill(); 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. * @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. * @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.

View File

@ -38,7 +38,7 @@ namespace skyline {
break; break;
} }
} }
} catch (std::exception &e) { } catch (const std::exception &e) {
state.logger->Error(e.what()); state.logger->Error(e.what());
} catch (...) { } catch (...) {
state.logger->Error("An unknown exception has occurred"); state.logger->Error("An unknown exception has occurred");
@ -109,7 +109,7 @@ namespace skyline {
ctx->registers.x0 = entryArg; ctx->registers.x0 = entryArg;
ctx->registers.x1 = handle; ctx->registers.x1 = handle;
ctx->state = ThreadState::WaitRun; 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); threadMap[thread->pid] = std::make_shared<std::thread>(&NCE::KernelThread, this, thread->pid);
} }

View File

@ -112,7 +112,7 @@ namespace skyline::guest {
"MOV LR, SP\n\t" "MOV LR, SP\n\t"
"SVC #0\n\t" "SVC #0\n\t"
"MOV SP, LR\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; break;
} }
default: { default: {
@ -179,6 +179,50 @@ namespace skyline::guest {
auto end = src + size; auto end = src + size;
while (src < end) while (src < end)
*(src++) = *(dest++); *(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 X27, XZR\n\t"
"MOV X28, XZR\n\t" "MOV X28, XZR\n\t"
"MOV X29, 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(); __builtin_unreachable();
} }
} }

View File

@ -5,9 +5,9 @@ namespace skyline {
constexpr size_t saveCtxSize = 20 * sizeof(u32); constexpr size_t saveCtxSize = 20 * sizeof(u32);
constexpr size_t loadCtxSize = 20 * sizeof(u32); constexpr size_t loadCtxSize = 20 * sizeof(u32);
#ifdef NDEBUG #ifdef NDEBUG
constexpr size_t svcHandlerSize = 175 * sizeof(u32); constexpr size_t svcHandlerSize = 225 * sizeof(u32);
#else #else
constexpr size_t svcHandlerSize = 275 * sizeof(u32); constexpr size_t svcHandlerSize = 400 * sizeof(u32);
#endif #endif
void entry(u64 address); void entry(u64 address);

View File

@ -136,6 +136,7 @@ namespace skyline {
enum class ThreadCall : u32 { enum class ThreadCall : u32 {
Syscall = 0x100, //!< A linux syscall needs to be called from the guest Syscall = 0x100, //!< A linux syscall needs to be called from the guest
Memcopy = 0x101, //!< To copy memory from one location to another Memcopy = 0x101, //!< To copy memory from one location to another
Clone = 0x102, //!< Use the clone syscall to create a new thread
}; };
/** /**

View File

@ -26,14 +26,13 @@ namespace skyline::kernel {
munmap(stack, stackSize); munmap(stack, stackSize);
throw exception("Failed to create guard pages"); 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; 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)); 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) if (pid == -1)
throw exception("Call to clone() has failed: {}", strerror(errno)); throw exception("Call to clone() has failed: {}", strerror(errno));
state.logger->Debug("Successfully created process with PID: {}", pid); 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); 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; return process;
} }

View File

@ -77,6 +77,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
} }
} }
thread(start = true) { thread(start = true) {
val snackbar = Snackbar.make(findViewById(android.R.id.content), getString(R.string.searching_roms), Snackbar.LENGTH_INDEFINITE)
runOnUiThread {snackbar.show()}
try { try {
runOnUiThread{adapter.clear()} runOnUiThread{adapter.clear()}
val entries = findFile("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!) 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!!) notifyUser(e.message!!)
} }
} }
runOnUiThread {snackbar.dismiss()}
} }
} }

View File

@ -34,4 +34,5 @@
<string name="handheld_enabled">The system will emulate being in handheld mode</string> <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="docked_enabled">The system will emulate being in docked mode</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="searching_roms">Searching for ROMs</string>
</resources> </resources>