diff options
Diffstat (limited to 'src/core/hle/kernel')
-rw-r--r-- | src/core/hle/kernel/address_arbiter.cpp | 7 | ||||
-rw-r--r-- | src/core/hle/kernel/event.cpp | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.cpp | 30 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.h | 33 | ||||
-rw-r--r-- | src/core/hle/kernel/mutex.cpp | 12 | ||||
-rw-r--r-- | src/core/hle/kernel/process.cpp | 98 | ||||
-rw-r--r-- | src/core/hle/kernel/process.h | 98 | ||||
-rw-r--r-- | src/core/hle/kernel/semaphore.cpp | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/session.h | 12 | ||||
-rw-r--r-- | src/core/hle/kernel/shared_memory.cpp | 33 | ||||
-rw-r--r-- | src/core/hle/kernel/shared_memory.h | 25 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.cpp | 131 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.h | 65 | ||||
-rw-r--r-- | src/core/hle/kernel/timer.cpp | 8 |
14 files changed, 406 insertions, 150 deletions
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 42f8ce2d9..a1221766e 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -3,8 +3,9 @@ // Refer to the license.txt file included. #include "common/common_types.h" +#include "common/logging/log.h" -#include "core/mem_map.h" +#include "core/memory.h" #include "core/hle/hle.h" #include "core/hle/kernel/address_arbiter.h" @@ -46,14 +47,12 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, case ArbitrationType::WaitIfLessThan: if ((s32)Memory::Read32(address) <= value) { Kernel::WaitCurrentThread_ArbitrateAddress(address); - HLE::Reschedule(__func__); } break; case ArbitrationType::WaitIfLessThanWithTimeout: if ((s32)Memory::Read32(address) <= value) { Kernel::WaitCurrentThread_ArbitrateAddress(address); GetCurrentThread()->WakeAfterDelay(nanoseconds); - HLE::Reschedule(__func__); } break; case ArbitrationType::DecrementAndWaitIfLessThan: @@ -62,7 +61,6 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, Memory::Write32(address, memory_value); if (memory_value <= value) { Kernel::WaitCurrentThread_ArbitrateAddress(address); - HLE::Reschedule(__func__); } break; } @@ -73,7 +71,6 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, if (memory_value <= value) { Kernel::WaitCurrentThread_ArbitrateAddress(address); GetCurrentThread()->WakeAfterDelay(nanoseconds); - HLE::Reschedule(__func__); } break; } diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 420906ec0..f338f3266 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -6,7 +6,7 @@ #include <algorithm> #include <vector> -#include "common/common.h" +#include "common/assert.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/event.h" diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 498b2ec98..b5c98b249 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -4,21 +4,20 @@ #include <algorithm> -#include "common/common.h" +#include "common/assert.h" +#include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" namespace Kernel { -unsigned int Object::next_object_id = 0; - -SharedPtr<Thread> g_main_thread = nullptr; +unsigned int Object::next_object_id; HandleTable g_handle_table; -u64 g_program_id = 0; void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); @@ -116,8 +115,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { return GetCurrentThread(); } else if (handle == CurrentProcess) { - LOG_ERROR(Kernel, "Current process (%08X) pseudo-handle not supported", CurrentProcess); - return nullptr; + return g_current_process; } if (!IsValid(handle)) { @@ -138,6 +136,11 @@ void HandleTable::Clear() { void Init() { Kernel::ThreadingInit(); Kernel::TimersInit(); + + Object::next_object_id = 0; + // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are + // reserved for low-level services + Process::next_process_id = 10; } /// Shutdown the kernel @@ -145,18 +148,7 @@ void Shutdown() { Kernel::ThreadingShutdown(); Kernel::TimersShutdown(); g_handle_table.Clear(); // Free all kernel objects -} - -/** - * Loads executable stored at specified address - * @entry_point Entry point in memory of loaded executable - * @return True on success, otherwise false - */ -bool LoadExec(u32 entry_point) { - // 0x30 is the typical main thread priority I've seen used so far - g_main_thread = Kernel::SetupMainThread(Kernel::DEFAULT_STACK_SIZE, entry_point, 0x30); - - return true; + g_current_process = nullptr; } } // namespace diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 2d295ea00..7c106d37c 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -7,21 +7,16 @@ #include <boost/intrusive_ptr.hpp> #include <array> +#include <memory> #include <string> #include <vector> -#include "common/common.h" -#include "core/hle/result.h" - -typedef u32 Handle; -typedef s32 Result; +#include "common/common_types.h" -// TODO: It would be nice to eventually replace these with strong types that prevent accidental -// conversion between each other. -typedef u32 VAddr; ///< Represents a pointer in the userspace virtual address space. -typedef u32 PAddr; ///< Represents a pointer in the ARM11 physical address space. +#include "core/hle/hle.h" +#include "core/hle/result.h" -const Handle INVALID_HANDLE = 0; +struct ApplicationInfo; namespace Kernel { @@ -95,12 +90,13 @@ public: return false; } +public: + static unsigned int next_object_id; + private: friend void intrusive_ptr_add_ref(Object*); friend void intrusive_ptr_release(Object*); - static unsigned int next_object_id; - unsigned int ref_count = 0; unsigned int object_id = next_object_id++; }; @@ -277,23 +273,10 @@ private: extern HandleTable g_handle_table; -/// The ID code of the currently running game -/// TODO(Subv): This variable should not be here, -/// we need a way to store information about the currently loaded application -/// for later query during runtime, maybe using the LDR service? -extern u64 g_program_id; - /// Initialize the kernel void Init(); /// Shutdown the kernel void Shutdown(); -/** - * Loads executable stored at specified address - * @entry_point Entry point in memory of loaded executable - * @return True on success, otherwise false - */ -bool LoadExec(u32 entry_point); - } // namespace diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index be2c49706..f530217fd 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -7,7 +7,7 @@ #include <boost/range/algorithm_ext/erase.hpp> -#include "common/common.h" +#include "common/assert.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" @@ -56,7 +56,15 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { } bool Mutex::ShouldWait() { - return lock_count > 0 && holding_thread != GetCurrentThread();; + auto thread = GetCurrentThread(); + bool wait = lock_count > 0 && holding_thread != thread; + + // If the holding thread of the mutex is lower priority than this thread, that thread should + // temporarily inherit this thread's priority + if (wait && thread->current_priority < holding_thread->current_priority) + holding_thread->BoostPriority(thread->current_priority); + + return wait; } void Mutex::Acquire() { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp new file mode 100644 index 000000000..0cdfa58d7 --- /dev/null +++ b/src/core/hle/kernel/process.cpp @@ -0,0 +1,98 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/logging/log.h" + +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/thread.h" +#include "core/memory.h" + +namespace Kernel { + +u32 Process::next_process_id; + +SharedPtr<Process> Process::Create(std::string name, u64 program_id) { + SharedPtr<Process> process(new Process); + + process->name = std::move(name); + process->program_id = program_id; + + process->flags.raw = 0; + process->flags.memory_region = MemoryRegion::APPLICATION; + + return process; +} + +void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { + for (int i = 0; i < len; ++i) { + u32 descriptor = kernel_caps[i]; + u32 type = descriptor >> 20; + + if (descriptor == 0xFFFFFFFF) { + // Unused descriptor entry + continue; + } else if ((type & 0xF00) == 0xE00) { // 0x0FFF + // Allowed interrupts list + LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored"); + } else if ((type & 0xF80) == 0xF00) { // 0x07FF + // Allowed syscalls mask + unsigned int index = ((descriptor >> 24) & 7) * 24; + u32 bits = descriptor & 0xFFFFFF; + + while (bits && index < svc_access_mask.size()) { + svc_access_mask.set(index, bits & 1); + ++index; bits >>= 1; + } + } else if ((type & 0xFF0) == 0xFE0) { // 0x00FF + // Handle table size + handle_table_size = descriptor & 0x3FF; + } else if ((type & 0xFF8) == 0xFF0) { // 0x007F + // Misc. flags + flags.raw = descriptor & 0xFFFF; + } else if ((type & 0xFFE) == 0xFF8) { // 0x001F + // Mapped memory range + if (i+1 >= len || ((kernel_caps[i+1] >> 20) & 0xFFE) != 0xFF8) { + LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored."); + continue; + } + u32 end_desc = kernel_caps[i+1]; + ++i; // Skip over the second descriptor on the next iteration + + AddressMapping mapping; + mapping.address = descriptor << 12; + mapping.size = (end_desc << 12) - mapping.address; + mapping.writable = descriptor & (1 << 20); + mapping.unk_flag = end_desc & (1 << 20); + + address_mappings.push_back(mapping); + } else if ((type & 0xFFF) == 0xFFE) { // 0x000F + // Mapped memory page + AddressMapping mapping; + mapping.address = descriptor << 12; + mapping.size = Memory::PAGE_SIZE; + mapping.writable = true; // TODO: Not sure if correct + mapping.unk_flag = false; + } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF + // Kernel version + int minor = descriptor & 0xFF; + int major = (descriptor >> 8) & 0xFF; + LOG_INFO(Loader, "ExHeader kernel version ignored: %d.%d", major, minor); + } else { + LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor); + } + } +} + +void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { + Kernel::SetupMainThread(entry_point, main_thread_priority); +} + +Kernel::Process::Process() {} +Kernel::Process::~Process() {} + +SharedPtr<Process> g_current_process; + +} diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h new file mode 100644 index 000000000..90881054c --- /dev/null +++ b/src/core/hle/kernel/process.h @@ -0,0 +1,98 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <bitset> + +#include <boost/container/static_vector.hpp> + +#include "common/bit_field.h" +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" +#include "core/hle/result.h" + +namespace Kernel { + +struct AddressMapping { + // Address and size must be page-aligned + VAddr address; + u32 size; + bool writable; + bool unk_flag; +}; + +enum class MemoryRegion : u16 { + APPLICATION = 1, + SYSTEM = 2, + BASE = 3, +}; + +union ProcessFlags { + u16 raw; + + BitField< 0, 1, u16> allow_debug; ///< Allows other processes to attach to and debug this process. + BitField< 1, 1, u16> force_debug; ///< Allows this process to attach to processes even if they don't have allow_debug set. + BitField< 2, 1, u16> allow_nonalphanum; + BitField< 3, 1, u16> shared_page_writable; ///< Shared page is mapped with write permissions. + BitField< 4, 1, u16> privileged_priority; ///< Can use priority levels higher than 24. + BitField< 5, 1, u16> allow_main_args; + BitField< 6, 1, u16> shared_device_mem; + BitField< 7, 1, u16> runnable_on_sleep; + BitField< 8, 4, MemoryRegion> memory_region; ///< Default region for memory allocations for this process + BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). +}; + +class Process final : public Object { +public: + static SharedPtr<Process> Create(std::string name, u64 program_id); + + std::string GetTypeName() const override { return "Process"; } + std::string GetName() const override { return name; } + + static const HandleType HANDLE_TYPE = HandleType::Process; + HandleType GetHandleType() const override { return HANDLE_TYPE; } + + static u32 next_process_id; + + /// Name of the process + std::string name; + /// Title ID corresponding to the process + u64 program_id; + + /// The process may only call SVCs which have the corresponding bit set. + std::bitset<0x80> svc_access_mask; + /// Maximum size of the handle table for the process. + unsigned int handle_table_size = 0x200; + /// Special memory ranges mapped into this processes address space. This is used to give + /// processes access to specific I/O regions and device memory. + boost::container::static_vector<AddressMapping, 8> address_mappings; + ProcessFlags flags; + + /// The id of this process + u32 process_id = next_process_id++; + + /// Bitmask of the used TLS slots + std::bitset<300> used_tls_slots; + + /** + * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them + * to this process. + */ + void ParseKernelCaps(const u32* kernel_caps, size_t len); + + /** + * Applies address space changes and launches the process main thread. + */ + void Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size); + +private: + Process(); + ~Process() override; +}; + +extern SharedPtr<Process> g_current_process; + +} diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index 6aecc24aa..5d6543ef4 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/common.h" +#include "common/assert.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/semaphore.h" diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h index 9e9288e0f..54a062971 100644 --- a/src/core/hle/kernel/session.h +++ b/src/core/hle/kernel/session.h @@ -5,19 +5,23 @@ #pragma once #include "core/hle/kernel/kernel.h" -#include "core/mem_map.h" +#include "core/hle/kernel/thread.h" +#include "core/memory.h" namespace Kernel { static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header /** - * Returns a pointer to the command buffer in kernel memory + * Returns a pointer to the command buffer in the current thread's TLS + * TODO(Subv): This is not entirely correct, the command buffer should be copied from + * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to + * the service handler process' memory. * @param offset Optional offset into command buffer * @return Pointer to command buffer */ -inline static u32* GetCommandBuffer(const int offset=0) { - return (u32*)Memory::GetPointer(Memory::KERNEL_MEMORY_VADDR + kCommandHeaderOffset + offset); +inline static u32* GetCommandBuffer(const int offset = 0) { + return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + offset); } /** diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index 4211fcf04..4137683b5 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -2,9 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/common.h" +#include <cstring> -#include "core/mem_map.h" +#include "common/logging/log.h" + +#include "core/memory.h" #include "core/hle/kernel/shared_memory.h" namespace Kernel { @@ -12,10 +14,15 @@ namespace Kernel { SharedMemory::SharedMemory() {} SharedMemory::~SharedMemory() {} -SharedPtr<SharedMemory> SharedMemory::Create(std::string name) { +SharedPtr<SharedMemory> SharedMemory::Create(u32 size, MemoryPermission permissions, + MemoryPermission other_permissions, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory); shared_memory->name = std::move(name); + shared_memory->base_address = 0x0; + shared_memory->size = size; + shared_memory->permissions = permissions; + shared_memory->other_permissions = other_permissions; return shared_memory; } @@ -23,7 +30,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(std::string name) { ResultCode SharedMemory::Map(VAddr address, MemoryPermission permissions, MemoryPermission other_permissions) { - if (address < Memory::SHARED_MEMORY_VADDR || address >= Memory::SHARED_MEMORY_VADDR_END) { + if (address < Memory::SHARED_MEMORY_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) { LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X outside of shared mem bounds!", GetObjectId(), address); // TODO: Verify error code with hardware @@ -31,21 +38,25 @@ ResultCode SharedMemory::Map(VAddr address, MemoryPermission permissions, ErrorSummary::InvalidArgument, ErrorLevel::Permanent); } + // TODO: Test permissions + + // HACK: Since there's no way to write to the memory block without mapping it onto the game + // process yet, at least initialize memory the first time it's mapped. + if (address != this->base_address) { + std::memset(Memory::GetPointer(address), 0, size); + } + this->base_address = address; - this->permissions = permissions; - this->other_permissions = other_permissions; return RESULT_SUCCESS; } -ResultVal<u8*> SharedMemory::GetPointer(u32 offset) { +u8* SharedMemory::GetPointer(u32 offset) { if (base_address != 0) - return MakeResult<u8*>(Memory::GetPointer(base_address + offset)); + return Memory::GetPointer(base_address + offset); LOG_ERROR(Kernel_SVC, "memory block id=%u not mapped!", GetObjectId()); - // TODO(yuriks): Verify error code. - return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, - ErrorSummary::InvalidState, ErrorLevel::Permanent); + return nullptr; } } // namespace diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 5833b411c..204266896 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -27,11 +27,16 @@ class SharedMemory final : public Object { public: /** * Creates a shared memory object - * @param name Optional object name, used only for debugging purposes. + * @param size Size of the memory block. Must be page-aligned. + * @param permissions Permission restrictions applied to the process which created the block. + * @param other_permissions Permission restrictions applied to other processes mapping the block. + * @param name Optional object name, used for debugging purposes. */ - static SharedPtr<SharedMemory> Create(std::string name = "Unknown"); + static SharedPtr<SharedMemory> Create(u32 size, MemoryPermission permissions, + MemoryPermission other_permissions, std::string name = "Unknown"); std::string GetTypeName() const override { return "SharedMemory"; } + std::string GetName() const override { return name; } static const HandleType HANDLE_TYPE = HandleType::SharedMemory; HandleType GetHandleType() const override { return HANDLE_TYPE; } @@ -49,12 +54,18 @@ public: * @param offset Offset from the start of the shared memory block to get pointer * @return Pointer to the shared memory block from the specified offset */ - ResultVal<u8*> GetPointer(u32 offset = 0); + u8* GetPointer(u32 offset = 0); - VAddr base_address; ///< Address of shared memory block in RAM - MemoryPermission permissions; ///< Permissions of shared memory block (SVC field) - MemoryPermission other_permissions; ///< Other permissions of shared memory block (SVC field) - std::string name; ///< Name of shared memory object (optional) + /// Address of shared memory block in the process. + VAddr base_address; + /// Size of the memory block. Page-aligned. + u32 size; + /// Permission restrictions applied to the process which created the block. + MemoryPermission permissions; + /// Permission restrictions applied to other processes mapping the block. + MemoryPermission other_permissions; + /// Name of shared memory object. + std::string name; private: SharedMemory(); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index be1aed615..a5f1904d7 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -6,7 +6,9 @@ #include <list> #include <vector> -#include "common/common.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/thread_queue_list.h" @@ -15,15 +17,16 @@ #include "core/core_timing.h" #include "core/hle/hle.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/mutex.h" #include "core/hle/result.h" -#include "core/mem_map.h" +#include "core/memory.h" namespace Kernel { /// Event type for the thread wake up event -static int ThreadWakeupEventType = -1; +static int ThreadWakeupEventType; bool Thread::ShouldWait() { return status != THREADSTATUS_DEAD; @@ -42,7 +45,7 @@ static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST+1> ready_queue; static Thread* current_thread; // The first available thread id at startup -static u32 next_thread_id = 1; +static u32 next_thread_id; /** * Creates a new thread ID @@ -104,6 +107,8 @@ void Thread::Stop() { for (auto& wait_object : wait_objects) { wait_object->RemoveWaitingThread(this); } + + Kernel::g_current_process->used_tls_slots[tls_index] = false; } Thread* ArbitrateHighestPriorityThread(u32 address) { @@ -140,17 +145,38 @@ void ArbitrateAllThreads(u32 address) { } } +/// Boost low priority threads (temporarily) that have been starved +static void PriorityBoostStarvedThreads() { + u64 current_ticks = CoreTiming::GetTicks(); + + for (auto& thread : thread_list) { + // TODO(bunnei): Threads that have been waiting to be scheduled for `boost_ticks` (or + // longer) will have their priority temporarily adjusted to 1 higher than the highest + // priority thread to prevent thread starvation. This general behavior has been verified + // on hardware. However, this is almost certainly not perfect, and the real CTR OS scheduler + // should probably be reversed to verify this. + + const u64 boost_timeout = 2000000; // Boost threads that have been ready for > this long + + u64 delta = current_ticks - thread->last_running_ticks; + + if (thread->status == THREADSTATUS_READY && delta > boost_timeout) { + const s32 priority = std::max(ready_queue.get_first()->current_priority - 1, 0); + thread->BoostPriority(priority); + } + } +} + /** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to */ static void SwitchContext(Thread* new_thread) { - DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, "Thread must be ready to become running."); - Thread* previous_thread = GetCurrentThread(); // Save context for previous thread if (previous_thread) { + previous_thread->last_running_ticks = CoreTiming::GetTicks(); Core::g_app_core->SaveContext(previous_thread->context); if (previous_thread->status == THREADSTATUS_RUNNING) { @@ -163,12 +189,18 @@ static void SwitchContext(Thread* new_thread) { // Load context of new thread if (new_thread) { + DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, "Thread must be ready to become running."); + current_thread = new_thread; ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; + // Restores thread to its nominal priority if it has been temporarily changed + new_thread->current_priority = new_thread->nominal_priority; + Core::g_app_core->LoadContext(new_thread->context); + Core::g_app_core->SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { current_thread = nullptr; } @@ -186,6 +218,10 @@ static Thread* PopNextReadyThread() { // We have to do better than the current thread. // This call returns null when that's not possible. next = ready_queue.pop_first_better(thread->current_priority); + if (!next) { + // Otherwise just keep going with the current thread + next = thread; + } } else { next = ready_queue.pop_first(); } @@ -364,7 +400,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, thread->status = THREADSTATUS_DORMANT; thread->entry_point = entry_point; thread->stack_top = stack_top; - thread->initial_priority = thread->current_priority = priority; + thread->nominal_priority = thread->current_priority = priority; + thread->last_running_ticks = CoreTiming::GetTicks(); thread->processor_id = processor_id; thread->wait_set_output = false; thread->wait_all = false; @@ -372,6 +409,20 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, thread->wait_address = 0; thread->name = std::move(name); thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom(); + thread->owner_process = g_current_process; + thread->tls_index = -1; + + // Find the next available TLS index, and mark it as used + auto& used_tls_slots = Kernel::g_current_process->used_tls_slots; + for (unsigned int i = 0; i < used_tls_slots.size(); ++i) { + if (used_tls_slots[i] == false) { + thread->tls_index = i; + used_tls_slots[i] = true; + break; + } + } + + ASSERT_MSG(thread->tls_index != -1, "Out of TLS space"); // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used // to initialize the context @@ -400,35 +451,26 @@ static void ClampPriority(const Thread* thread, s32* priority) { void Thread::SetPriority(s32 priority) { ClampPriority(this, &priority); - if (current_priority == priority) { - return; - } - - if (status == THREADSTATUS_READY) { - // If thread was ready, adjust queues - ready_queue.remove(current_priority, this); + // If thread was ready, adjust queues + if (status == THREADSTATUS_READY) + ready_queue.move(this, current_priority, priority); + else ready_queue.prepare(priority); - ready_queue.push_back(priority, this); - } - - current_priority = priority; -} -SharedPtr<Thread> SetupIdleThread() { - // We need to pass a few valid values to get around parameter checking in Thread::Create. - auto thread = Thread::Create("idle", Memory::KERNEL_MEMORY_VADDR, THREADPRIO_LOWEST, 0, - THREADPROCESSORID_0, 0).MoveFrom(); + nominal_priority = current_priority = priority; +} - thread->idle = true; - return thread; +void Thread::BoostPriority(s32 priority) { + ready_queue.move(this, current_priority, priority); + current_priority = priority; } -SharedPtr<Thread> SetupMainThread(u32 stack_size, u32 entry_point, s32 priority) { +SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { DEBUG_ASSERT(!GetCurrentThread()); // Initialize new "main" thread auto thread_res = Thread::Create("main", entry_point, priority, 0, - THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END); + THREADPROCESSORID_0, Memory::HEAP_VADDR_END); SharedPtr<Thread> thread = thread_res.MoveFrom(); @@ -439,21 +481,25 @@ SharedPtr<Thread> SetupMainThread(u32 stack_size, u32 entry_point, s32 priority) } void Reschedule() { - Thread* prev = GetCurrentThread(); + PriorityBoostStarvedThreads(); + + Thread* cur = GetCurrentThread(); Thread* next = PopNextReadyThread(); HLE::g_reschedule = false; - if (next != nullptr) { - LOG_TRACE(Kernel, "context switch %u -> %u", prev->GetObjectId(), next->GetObjectId()); - SwitchContext(next); - } else { - LOG_TRACE(Kernel, "cannot context switch from %u, no higher priority thread!", prev->GetObjectId()); + // Don't bother switching to the same thread + if (next == cur) + return; - for (auto& thread : thread_list) { - LOG_TRACE(Kernel, "\tid=%u prio=0x%02X, status=0x%08X", thread->GetObjectId(), - thread->current_priority, thread->status); - } + if (cur && next) { + LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); + } else if (cur) { + LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId()); + } else if (next) { + LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId()); } + + SwitchContext(next); } void Thread::SetWaitSynchronizationResult(ResultCode result) { @@ -464,13 +510,20 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { context.cpu_registers[1] = output; } +VAddr Thread::GetTLSAddress() const { + return Memory::TLS_AREA_VADDR + tls_index * 0x200; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback); - // Setup the idle thread - SetupIdleThread(); + current_thread = nullptr; + next_thread_id = 1; + + thread_list.clear(); + ready_queue.clear(); } void ThreadingShutdown() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index cfd073a70..389928178 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -12,22 +12,23 @@ #include "common/common_types.h" #include "core/core.h" -#include "core/mem_map.h" #include "core/hle/kernel/kernel.h" #include "core/hle/result.h" -enum ThreadPriority { - THREADPRIO_HIGHEST = 0, ///< Highest thread priority - THREADPRIO_DEFAULT = 16, ///< Default thread priority for userland apps - THREADPRIO_LOW = 31, ///< Low range of thread priority for userland apps - THREADPRIO_LOWEST = 63, ///< Thread priority max checked by svcCreateThread +enum ThreadPriority : s32{ + THREADPRIO_HIGHEST = 0, ///< Highest thread priority + THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps + THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps + THREADPRIO_LOWEST = 63, ///< Lowest thread priority }; -enum ThreadProcessorId { - THREADPROCESSORID_0 = 0xFFFFFFFE, ///< Enables core appcode - THREADPROCESSORID_1 = 0xFFFFFFFD, ///< Enables core syscore - THREADPROCESSORID_ALL = 0xFFFFFFFC, ///< Enables both cores +enum ThreadProcessorId : s32 { + THREADPROCESSORID_DEFAULT = -2, ///< Run thread on default core specified by exheader + THREADPROCESSORID_ALL = -1, ///< Run thread on either core + THREADPROCESSORID_0 = 0, ///< Run thread on core 0 (AppCore) + THREADPROCESSORID_1 = 1, ///< Run thread on core 1 (SysCore) + THREADPROCESSORID_MAX = 2, ///< Processor ID must be less than this }; enum ThreadStatus { @@ -43,6 +44,7 @@ enum ThreadStatus { namespace Kernel { class Mutex; +class Process; class Thread final : public WaitObject { public: @@ -70,12 +72,6 @@ public: void Acquire() override; /** - * Checks if the thread is an idle (stub) thread - * @return True if the thread is an idle (stub) thread, false otherwise - */ - inline bool IsIdle() const { return idle; } - - /** * Gets the thread's current priority * @return The current thread's priority */ @@ -88,6 +84,12 @@ public: void SetPriority(s32 priority); /** + * Temporarily boosts the thread's priority until the next time it is scheduled + * @param priority The new priority + */ + void BoostPriority(s32 priority); + + /** * Gets the thread's thread ID * @return The thread's ID */ @@ -127,6 +129,12 @@ public: */ void Stop(); + /* + * Returns the Thread Local Storage address of the current thread + * @returns VAddr of the thread's TLS + */ + VAddr GetTLSAddress() const; + Core::ThreadContext context; u32 thread_id; @@ -135,14 +143,19 @@ public: u32 entry_point; u32 stack_top; - s32 initial_priority; - s32 current_priority; + s32 nominal_priority; ///< Nominal thread priority, as set by the emulated application + s32 current_priority; ///< Current thread priority, can be temporarily changed + + u64 last_running_ticks; ///< CPU tick when thread was last running s32 processor_id; + s32 tls_index; ///< Index of the Thread Local Storage of the thread + /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; + SharedPtr<Process> owner_process; ///< Process that owns this thread std::vector<SharedPtr<WaitObject>> wait_objects; ///< Objects that the thread is waiting on VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address bool wait_all; ///< True if the thread is waiting on all objects before resuming @@ -150,9 +163,6 @@ public: std::string name; - /// Whether this thread is intended to never actually be executed, i.e. always idle - bool idle = false; - private: Thread(); ~Thread() override; @@ -161,16 +171,13 @@ private: Handle callback_handle; }; -extern SharedPtr<Thread> g_main_thread; - /** * Sets up the primary application thread - * @param stack_size The size of the thread's stack * @param entry_point The address at which the thread should start execution * @param priority The priority to give the main thread * @return A shared pointer to the main thread */ -SharedPtr<Thread> SetupMainThread(u32 stack_size, u32 entry_point, s32 priority); +SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority); /** * Reschedules to the next available thread (call after current thread is suspended) @@ -214,14 +221,6 @@ void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wa void WaitCurrentThread_ArbitrateAddress(VAddr wait_address); /** - * Sets up the idle thread, this is a thread that is intended to never execute instructions, - * only to advance the timing. It is scheduled when there are no other ready threads in the thread queue - * and will try to yield on every call. - * @return The handle of the idle thread - */ -SharedPtr<Thread> SetupIdleThread(); - -/** * Initialize threading */ void ThreadingInit(); diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index 610e26a3c..e69fece65 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -2,7 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/common.h" +#include "common/assert.h" +#include "common/logging/log.h" #include "core/core_timing.h" #include "core/hle/kernel/kernel.h" @@ -12,7 +13,7 @@ namespace Kernel { /// The event type of the generic timer callback event -static int timer_callback_event_type = -1; +static int timer_callback_event_type; // TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future, allowing // us to simply use a pool index or similar. static Kernel::HandleTable timer_callback_handle_table; @@ -66,7 +67,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle)); if (timer == nullptr) { - LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08X", timer_handle); + LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08lX", timer_handle); return; } @@ -89,6 +90,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { } void TimersInit() { + timer_callback_handle_table.Clear(); timer_callback_event_type = CoreTiming::RegisterEvent("TimerCallback", TimerCallback); } |