aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/hle/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/kernel')
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp7
-rw-r--r--src/core/hle/kernel/event.cpp2
-rw-r--r--src/core/hle/kernel/kernel.cpp30
-rw-r--r--src/core/hle/kernel/kernel.h33
-rw-r--r--src/core/hle/kernel/mutex.cpp12
-rw-r--r--src/core/hle/kernel/process.cpp98
-rw-r--r--src/core/hle/kernel/process.h98
-rw-r--r--src/core/hle/kernel/semaphore.cpp2
-rw-r--r--src/core/hle/kernel/session.h12
-rw-r--r--src/core/hle/kernel/shared_memory.cpp33
-rw-r--r--src/core/hle/kernel/shared_memory.h25
-rw-r--r--src/core/hle/kernel/thread.cpp131
-rw-r--r--src/core/hle/kernel/thread.h65
-rw-r--r--src/core/hle/kernel/timer.cpp8
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 42f8ce2d..a1221766 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 420906ec..f338f326 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 498b2ec9..b5c98b24 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 2d295ea0..7c106d37 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 be2c4970..f530217f 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 00000000..0cdfa58d
--- /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 00000000..90881054
--- /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 6aecc24a..5d6543ef 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 9e9288e0..54a06297 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 4211fcf0..4137683b 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 5833b411..20426689 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 be1aed61..a5f1904d 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 cfd073a7..38992817 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 610e26a3..e69fece6 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);
}