From b2b1129cc035ea2321e0dec4136d22d07e54b930 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 31 Jan 2020 01:53:30 -0800 Subject: [PATCH] kern: mostly implement KThread::Initialize --- .../include/mesosphere/kern_k_process.hpp | 5 + .../include/mesosphere/kern_k_thread.hpp | 58 ++++- .../include/mesosphere/kern_k_worker_task.hpp | 33 +++ .../include/mesosphere/kern_kernel.hpp | 17 +- .../mesosphere/svc/kern_svc_results.hpp | 2 + .../libmesosphere/source/kern_k_thread.cpp | 205 ++++++++++++++++++ .../libmesosphere/source/kern_kernel.cpp | 21 +- libraries/libmesosphere/source/kern_main.cpp | 4 +- .../libmesosphere/source/libc/kern_cxx.c | 27 +++ .../libmesosphere/source/libc/kern_new.cpp | 24 ++ .../include/vapours/results/svc_results.hpp | 2 + 11 files changed, 383 insertions(+), 15 deletions(-) create mode 100644 libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp create mode 100644 libraries/libmesosphere/source/kern_k_thread.cpp create mode 100644 libraries/libmesosphere/source/libc/kern_cxx.c create mode 100644 libraries/libmesosphere/source/libc/kern_new.cpp diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp index 2d3e2b21c..9c93e55c2 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -26,6 +26,11 @@ namespace ams::kern { class KProcess final : public KAutoObjectWithSlabHeapAndContainer { MESOSPHERE_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject); /* TODO: This is a placeholder definition. */ + public: + constexpr ALWAYS_INLINE u64 GetCoreMask() const { /* TODO */ return 0; } + constexpr ALWAYS_INLINE u64 GetPriorityMask() const { /* TODO */ return 0; } + + constexpr ALWAYS_INLINE bool Is64Bit() const { /* TODO */ return true; } }; } diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp index 02a8d60ba..3f1c58ab1 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp @@ -19,12 +19,19 @@ #include #include #include +#include +#include namespace ams::kern { - class KThread final : public KAutoObjectWithSlabHeapAndContainer { + using KThreadFunction = void (*)(uintptr_t); + + class KThread final : public KAutoObjectWithSlabHeapAndContainer, public KTimerTask, public KWorkerTask { MESOSPHERE_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject); public: + static constexpr s32 MainThreadPriority = 1; + static constexpr s32 IdleThreadPriority = 64; + enum ThreadType : u32 { ThreadType_Main = 0, ThreadType_Kernel = 1, @@ -36,6 +43,10 @@ namespace ams::kern { SuspendType_Process = 0, SuspendType_Thread = 1, SuspendType_Debug = 2, + SuspendType_Unk3 = 3, + SuspendType_Unk4 = 4, + + SuspendType_Count, }; enum ThreadState : u16 { @@ -50,6 +61,10 @@ namespace ams::kern { ThreadState_ProcessSuspended = (1 << (SuspendType_Process + ThreadState_SuspendShift)), ThreadState_ThreadSuspended = (1 << (SuspendType_Thread + ThreadState_SuspendShift)), ThreadState_DebugSuspended = (1 << (SuspendType_Debug + ThreadState_SuspendShift)), + ThreadState_Unk3Suspended = (1 << (SuspendType_Unk3 + ThreadState_SuspendShift)), + ThreadState_Unk4Suspended = (1 << (SuspendType_Unk4 + ThreadState_SuspendShift)), + + ThreadState_SuspendFlagMask = ((1 << SuspendType_Count) - 1) << ThreadState_SuspendShift, }; enum DpcFlag : u32 { @@ -76,6 +91,11 @@ namespace ams::kern { public: constexpr ALWAYS_INLINE QueueEntry() : prev(nullptr), next(nullptr) { /* ... */ } + constexpr ALWAYS_INLINE void Initialize() { + this->prev = nullptr; + this->next = nullptr; + } + constexpr ALWAYS_INLINE KThread *GetPrev() const { return this->prev; } constexpr ALWAYS_INLINE KThread *GetNext() const { return this->next; } constexpr ALWAYS_INLINE void SetPrev(KThread *t) { this->prev = t; } @@ -90,6 +110,8 @@ namespace ams::kern { constexpr SyncObjectBuffer() : sync_objects() { /* ... */ } }; static_assert(sizeof(SyncObjectBuffer::sync_objects) == sizeof(SyncObjectBuffer::handles)); + private: + static inline std::atomic s_next_thread_id = 0; private: alignas(16) KThreadContext thread_context; KAffinityMask affinity_mask; @@ -141,25 +163,27 @@ namespace ams::kern { std::atomic termination_requested; bool ipc_cancelled; bool wait_cancelled; - bool cancelable; + bool cancellable; bool registered; bool signaled; bool initialized; bool debug_attached; s8 priority_inheritance_count; bool resource_limit_release_hint; - public: - static void PostDestroy(uintptr_t arg); public: explicit KThread() /* TODO: : ? */ { MESOSPHERE_ASSERT_THIS(); } + virtual ~KThread() { /* ... */ } /* TODO: Is a constexpr KThread() possible? */ + + Result Initialize(KThreadFunction func, uintptr_t arg, void *kern_stack_top, KProcessAddress user_stack_top, s32 prio, s32 core, KProcess *owner, ThreadType type); + private: StackParameters &GetStackParameters() { return *(reinterpret_cast(this->kernel_stack_top) - 1); } const StackParameters &GetStackParameters() const { - return *(reinterpret_cast(this->kernel_stack_top) - 1); + return *(reinterpret_cast(this->kernel_stack_top) - 1); } public: ALWAYS_INLINE s32 GetDisableDispatchCount() const { @@ -178,13 +202,33 @@ namespace ams::kern { MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() > 0); GetStackParameters().disable_count--; } - public: constexpr ALWAYS_INLINE const KAffinityMask &GetAffinityMask() const { return this->affinity_mask; } + ALWAYS_INLINE void *GetStackTop() const { return reinterpret_cast(this->kernel_stack_top) - 1; } + ALWAYS_INLINE void *GetKernelStackTop() const { return this->kernel_stack_top; } + /* TODO: This is kind of a placeholder definition. */ - /* TODO: This is a placeholder definition. */ + ALWAYS_INLINE bool IsInExceptionHandler() const { + return GetStackParameters().is_in_exception_handler; + } + + ALWAYS_INLINE void SetInExceptionHandler() { + GetStackParameters().is_in_exception_handler = true; + } + + public: + /* Overridden parent functions. */ + virtual bool IsInitialized() const override { return this->initialized; } + virtual uintptr_t GetPostDestroyArgument() const override { return reinterpret_cast(this->parent) | (this->resource_limit_release_hint ? 1 : 0); } + + static void PostDestroy(uintptr_t arg); + + virtual void Finalize() override; + virtual bool IsSignaled() const override; + virtual void OnTimer() override; + virtual void DoWorkerTask() override; public: static constexpr bool IsWaiterListValid() { return WaiterListTraits::IsValid(); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp new file mode 100644 index 000000000..7620adb6a --- /dev/null +++ b/libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::kern { + + class KWorkerTask { + private: + KWorkerTask *next_task; + public: + constexpr ALWAYS_INLINE KWorkerTask() : next_task(nullptr) { /* ... */ } + + constexpr ALWAYS_INLINE KWorkerTask *GetNextTask() const { return this->next_task; } + constexpr ALWAYS_INLINE void SetNextTask(KWorkerTask *task) { this->next_task = task; } + + virtual void DoWorkerTask() = 0; + }; + +} diff --git a/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp b/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp index c1b95e15a..600f142e0 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp @@ -14,10 +14,13 @@ * along with this program. If not, see . */ #pragma once +#include #include +#include #include #include #include +#include namespace ams::kern { @@ -30,12 +33,22 @@ namespace ams::kern { }; private: static inline State s_state = State::Invalid; + static inline KThread s_main_threads[cpu::NumCores]; + static inline KThread s_idle_threads[cpu::NumCores]; public: - static NOINLINE void Initialize(s32 core_id); - static NOINLINE void InitializeCoreThreads(s32 core_id); + static NOINLINE void InitializeCoreLocalRegion(s32 core_id); + static NOINLINE void InitializeMainAndIdleThreads(s32 core_id); static ALWAYS_INLINE State GetState() { return s_state; } static ALWAYS_INLINE void SetState(State state) { s_state = state; } + + static ALWAYS_INLINE KThread &GetMainThread(s32 core_id) { + return s_main_threads[core_id]; + } + + static ALWAYS_INLINE KThread &GetIdleThread(s32 core_id) { + return s_idle_threads[core_id]; + } }; } diff --git a/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp b/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp index 29eb82690..6ff150a7d 100644 --- a/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp +++ b/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp @@ -24,6 +24,8 @@ namespace ams::kern::svc { /* 33 */ using ::ams::svc::ResultNotImplemented; + /* 57 */ using ::ams::svc::ResultNoSynchronizationObject; + /* 59 */ using ::ams::svc::ResultThreadTerminating; /* 70 */ using ::ams::svc::ResultNoEvent; diff --git a/libraries/libmesosphere/source/kern_k_thread.cpp b/libraries/libmesosphere/source/kern_k_thread.cpp new file mode 100644 index 000000000..41c03f839 --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_thread.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::kern { + + Result KThread::Initialize(KThreadFunction func, uintptr_t arg, void *kern_stack_top, KProcessAddress user_stack_top, s32 prio, s32 core, KProcess *owner, ThreadType type) { + /* Assert parameters are valid. */ + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(kern_stack_top != nullptr); + MESOSPHERE_ASSERT((type == ThreadType_Main) || (ams::svc::HighestThreadPriority <= prio && prio <= ams::svc::LowestThreadPriority)); + MESOSPHERE_ASSERT((owner != nullptr) || (type != ThreadType_User)); + MESOSPHERE_ASSERT(0 <= core && core < static_cast(cpu::NumCores)); + + /* First, clear the TLS address. */ + this->tls_address = Null; + + const uintptr_t kern_stack_top_address = reinterpret_cast(kern_stack_top); + + /* Next, assert things based on the type. */ + switch (type) { + case ThreadType_Main: + { + MESOSPHERE_ASSERT(arg == 0); + } + [[fallthrough]]; + case ThreadType_HighPriority: + { + MESOSPHERE_ASSERT(core == GetCurrentCoreId()); + } + [[fallthrough]]; + case ThreadType_Kernel: + { + MESOSPHERE_ASSERT(user_stack_top == 0); + MESOSPHERE_ASSERT(util::IsAligned(kern_stack_top_address, PageSize)); + } + [[fallthrough]]; + case ThreadType_User: + { + MESOSPHERE_ASSERT((owner == nullptr) || (owner->GetCoreMask() | (1ul << core)) == owner->GetCoreMask()); + MESOSPHERE_ASSERT((owner == nullptr) || (owner->GetPriorityMask() | (1ul << prio)) == owner->GetPriorityMask()); + } + break; + default: + MESOSPHERE_PANIC("KThread::Initialize: Unknown ThreadType %u", static_cast(type)); + break; + } + + /* Set the ideal core ID and affinity mask. */ + this->ideal_core_id = core; + this->affinity_mask.SetAffinity(core, true); + + /* Set the thread state. */ + this->thread_state = (type == ThreadType_Main) ? ThreadState_Runnable : ThreadState_Initialized; + + /* Set TLS address and TLS heap address. */ + /* NOTE: Nintendo wrote TLS address above already, but official code really does write tls address twice. */ + this->tls_address = 0; + this->tls_heap_address = 0; + + /* Set parent and condvar tree. */ + this->parent = nullptr; + this->cond_var_tree = nullptr; + + /* Set sync booleans. */ + this->signaled = false; + this->ipc_cancelled = false; + this->termination_requested = false; + this->wait_cancelled = false; + this->cancellable = false; + + /* Set core ID and wait result. */ + this->core_id = this->ideal_core_id; + this->wait_result = svc::ResultNoSynchronizationObject(); + + /* Set the stack top. */ + this->kernel_stack_top = kern_stack_top; + + /* Set priorities. */ + this->priority = prio; + this->base_priority = prio; + + /* Set sync object and waiting lock to null. */ + this->synced_object = nullptr; + this->waiting_lock = nullptr; + + /* Initialize sleeping queue. */ + this->sleeping_queue_entry.Initialize(); + this->sleeping_queue = nullptr; + + /* Set suspend flags. */ + this->suspend_request_flags = 0; + this->suspend_allowed_flags = ThreadState_SuspendFlagMask; + + /* We're neither debug attached, nor are we nesting our priority inheritance. */ + this->debug_attached = false; + this->priority_inheritance_count = 0; + + /* We haven't been scheduled, and we have done no light IPC. */ + this->schedule_count = -1; + this->last_scheduled_tick = 0; + this->light_ipc_data = nullptr; + + /* We're not waiting for a lock, and we haven't disabled migration. */ + this->lock_owner = nullptr; + this->num_core_migration_disables = 0; + + /* We have no waiters, but we do have an entrypoint. */ + this->num_kernel_waiters = 0; + this->entrypoint = reinterpret_cast(func); + + /* We don't need a release (probably), and we've spent no time on the cpu. */ + this->resource_limit_release_hint = 0; + this->cpu_time = 0; + + /* Clear our stack parameters. */ + std::memset(static_cast(std::addressof(this->GetStackParameters())), 0, sizeof(StackParameters)); + + /* Setup the TLS, if needed. */ + if (type == ThreadType_User) { + /* TODO: R_TRY(owner->CreateThreadLocalRegion(&this->tls_address)); */ + /* TODO: this->tls_heap_address = owner->GetThreadLocalRegionAddress(this->tls_address); */ + std::memset(this->tls_heap_address, 0, ams::svc::ThreadLocalRegionSize); + } + + /* Set parent, if relevant. */ + if (owner != nullptr) { + this->parent = owner; + this->parent->Open(); + /* TODO: this->parent->IncrementThreadCount(); */ + } + + /* Initialize thread context. */ + constexpr bool IsDefault64Bit = sizeof(uintptr_t) == sizeof(u64); + const bool is_64_bit = this->parent ? this->parent->Is64Bit() : IsDefault64Bit; + const bool is_user = (type == ThreadType_User); + const bool is_main = (type == ThreadType_Main); + this->thread_context.Initialize(this->entrypoint, reinterpret_cast(this->GetStackTop()), GetInteger(user_stack_top), arg, is_user, is_64_bit, is_main); + + /* Setup the stack parameters. */ + StackParameters &sp = this->GetStackParameters(); + if (this->parent != nullptr) { + /* TODO: this->parent->CopySvcPermissionTo(pos.svc_permission); */ + } + sp.context = std::addressof(this->thread_context); + sp.disable_count = 1; + this->SetInExceptionHandler(); + + /* Set thread ID. */ + this->thread_id = s_next_thread_id++; + + /* We initialized! */ + this->initialized = true; + + /* Register ourselves with our parent process. */ + if (this->parent != nullptr) { + /* TODO: this->parent->RegisterThread(this); */ + /* TODO: if (this->parent->IsSuspended()) { this->RequestSuspend(SuspendType_Process); } */ + } + + return ResultSuccess(); + } + + void KThread::PostDestroy(uintptr_t arg) { + KProcess *owner = reinterpret_cast(arg & ~1ul); + const bool resource_limit_release_hint = (arg & 1); + if (owner != nullptr) { + /* TODO: Release from owner resource limit. */ + (void)(resource_limit_release_hint); + owner->Close(); + } else { + /* TODO: Release from system resource limit. */ + } + } + + void KThread::Finalize() { + /* TODO */ + } + + bool KThread::IsSignaled() const { + return this->signaled; + } + + void KThread::OnTimer() { + /* TODO */ + } + + void KThread::DoWorkerTask() { + /* TODO */ + } + +} diff --git a/libraries/libmesosphere/source/kern_kernel.cpp b/libraries/libmesosphere/source/kern_kernel.cpp index ea5b5a4f5..7c7c65503 100644 --- a/libraries/libmesosphere/source/kern_kernel.cpp +++ b/libraries/libmesosphere/source/kern_kernel.cpp @@ -17,7 +17,7 @@ namespace ams::kern { - void Kernel::Initialize(s32 core_id) { + void Kernel::InitializeCoreLocalRegion(s32 core_id) { /* Construct the core local region object in place. */ KCoreLocalContext *clc = GetPointer(KMemoryLayout::GetCoreLocalRegionAddress()); new (clc) KCoreLocalContext; @@ -46,9 +46,22 @@ namespace ams::kern { } } - void Kernel::InitializeCoreThreads(s32 core_id) { - /* TODO: This function wants to setup the main thread and the idle thread. */ - /* It also wants to initialize the scheduler/interrupt manager/hardware timer. */ + void Kernel::InitializeMainAndIdleThreads(s32 core_id) { + /* This function wants to setup the main thread and the idle thread. */ + KThread *main_thread = std::addressof(Kernel::GetMainThread(core_id)); + void *main_thread_stack = GetVoidPointer(KMemoryLayout::GetMainStackTopAddress(core_id)); + KThread *idle_thread = std::addressof(Kernel::GetIdleThread(core_id)); + void *idle_thread_stack = GetVoidPointer(KMemoryLayout::GetIdleStackTopAddress(core_id)); + KAutoObject::Create(main_thread); + KAutoObject::Create(idle_thread); + main_thread->Initialize(nullptr, 0, main_thread_stack, 0, KThread::MainThreadPriority, core_id, nullptr, KThread::ThreadType_Main); + idle_thread->Initialize(nullptr, 0, idle_thread_stack, 0, KThread::IdleThreadPriority, core_id, nullptr, KThread::ThreadType_Main); + + /* Set the current thread to be the main thread, and we have no processes running yet. */ + SetCurrentThread(main_thread); + SetCurrentProcess(nullptr); + + /* TODO: We also want to initialize the scheduler/interrupt manager/hardware timer. */ } } diff --git a/libraries/libmesosphere/source/kern_main.cpp b/libraries/libmesosphere/source/kern_main.cpp index bad569bfc..0a773e47a 100644 --- a/libraries/libmesosphere/source/kern_main.cpp +++ b/libraries/libmesosphere/source/kern_main.cpp @@ -19,7 +19,7 @@ namespace ams::kern { NORETURN void HorizonKernelMain(s32 core_id) { /* Setup the Core Local Region, and note that we're initializing. */ - Kernel::Initialize(core_id); + Kernel::InitializeCoreLocalRegion(core_id); Kernel::SetState(Kernel::State::Initializing); /* Ensure that all cores get to this point before proceeding. */ @@ -29,7 +29,7 @@ namespace ams::kern { /* Synchronize after each init to ensure the cores go in order. */ for (size_t i = 0; i < cpu::NumCores; i++) { if (static_cast(i) == core_id) { - Kernel::InitializeCoreThreads(core_id); + Kernel::InitializeMainAndIdleThreads(core_id); } cpu::SynchronizeAllCores(); } diff --git a/libraries/libmesosphere/source/libc/kern_cxx.c b/libraries/libmesosphere/source/libc/kern_cxx.c new file mode 100644 index 000000000..9d3dbd0de --- /dev/null +++ b/libraries/libmesosphere/source/libc/kern_cxx.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +void __dso_handle() { /* ... */ } +void __cxa_atexit() { /* ... */ } + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/libraries/libmesosphere/source/libc/kern_new.cpp b/libraries/libmesosphere/source/libc/kern_new.cpp new file mode 100644 index 000000000..8ce1e726f --- /dev/null +++ b/libraries/libmesosphere/source/libc/kern_new.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +void operator delete (void *deleted) throw() { + MESOSPHERE_PANIC("operator delete(void *) was called: %p", deleted); +} + +void operator delete (void *deleted, size_t size) throw() { + MESOSPHERE_PANIC("operator delete(void *, size_t) was called: %p %zu", deleted, size); +} \ No newline at end of file diff --git a/libraries/libvapours/include/vapours/results/svc_results.hpp b/libraries/libvapours/include/vapours/results/svc_results.hpp index 6dfbe9b6c..ecdcffefe 100644 --- a/libraries/libvapours/include/vapours/results/svc_results.hpp +++ b/libraries/libvapours/include/vapours/results/svc_results.hpp @@ -27,6 +27,8 @@ namespace ams::svc { R_DEFINE_ERROR_RESULT(NotImplemented, 33); + R_DEFINE_ERROR_RESULT(NoSynchronizationObject, 57); + R_DEFINE_ERROR_RESULT(ThreadTerminating, 59); R_DEFINE_ERROR_RESULT(NoEvent, 70);