2014-05-10 04:11:18 +02:00
|
|
|
// Copyright 2014 Citra Emulator Project / PPSSPP Project
|
2014-12-17 06:38:14 +01:00
|
|
|
// Licensed under GPLv2 or any later version
|
2014-11-19 09:49:13 +01:00
|
|
|
// Refer to the license.txt file included.
|
2014-05-10 04:11:18 +02:00
|
|
|
|
2014-06-06 04:35:36 +02:00
|
|
|
#include <algorithm>
|
2014-08-18 05:03:22 +02:00
|
|
|
#include <list>
|
2018-10-19 04:19:29 +02:00
|
|
|
#include <unordered_map>
|
2014-08-18 05:03:22 +02:00
|
|
|
#include <vector>
|
2015-05-06 09:06:12 +02:00
|
|
|
#include "common/assert.h"
|
|
|
|
#include "common/common_types.h"
|
|
|
|
#include "common/logging/log.h"
|
2015-02-19 07:46:21 +01:00
|
|
|
#include "common/math_util.h"
|
2014-12-22 07:30:09 +01:00
|
|
|
#include "core/arm/arm_interface.h"
|
2015-07-26 03:10:41 +02:00
|
|
|
#include "core/arm/skyeye_common/armstate.h"
|
2014-05-14 04:00:11 +02:00
|
|
|
#include "core/core.h"
|
2017-05-21 09:11:36 +02:00
|
|
|
#include "core/hle/kernel/errors.h"
|
2017-05-30 01:45:42 +02:00
|
|
|
#include "core/hle/kernel/handle_table.h"
|
2014-05-10 04:11:18 +02:00
|
|
|
#include "core/hle/kernel/kernel.h"
|
2015-11-27 04:00:16 +01:00
|
|
|
#include "core/hle/kernel/memory.h"
|
2014-12-07 21:44:21 +01:00
|
|
|
#include "core/hle/kernel/mutex.h"
|
2016-09-18 02:38:01 +02:00
|
|
|
#include "core/hle/kernel/process.h"
|
2016-09-21 08:52:38 +02:00
|
|
|
#include "core/hle/kernel/thread.h"
|
2014-10-23 05:20:01 +02:00
|
|
|
#include "core/hle/result.h"
|
2015-05-13 03:38:29 +02:00
|
|
|
#include "core/memory.h"
|
2014-05-10 04:11:18 +02:00
|
|
|
|
2014-05-21 01:37:46 +02:00
|
|
|
namespace Kernel {
|
2014-05-14 04:00:11 +02:00
|
|
|
|
Port various minor changes from yuzu PRs (#4725)
* common/thread: Remove unused functions
Many of these functions are carried over from Dolphin (where they aren't
used anymore). Given these have no use (and we really shouldn't be
screwing around with OS-specific thread scheduler handling from the
emulator, these can be removed.
The function for setting the thread name is left, however, since it can
have debugging utility usages.
* input_common/sdl: Use a type alias to shorten declaration of GetPollers
Just makes the definitions a little bit more tidy.
* input_common/sdl: Correct return values within implementations of GetPollers()
In both cases, we weren't actually returning anything, which is
undefined behavior.
* yuzu/debugger/graphics_surface: Fill in missing surface format listings
Fills in the missing surface types that were marked as unknown. The
order corresponds with the TextureFormat enum within
video_core/texture.h.
We also don't need to all of these strings as translatable (only the
first string, as it's an English word).
* yuzu/debugger/graphics_surface: Clean up connection overload deduction
We can utilize qOverload with the signal connections to make the
function deducing a little less ugly.
* yuzu/debugger/graphics_surface: Tidy up SaveSurface
- Use QStringLiteral where applicable.
- Use const where applicable
- Remove unnecessary precondition check (we already assert the pixbuf
being non null)
* yuzu/debugger/graphics_surface: Display error messages for file I/O errors
* core: Add missing override specifiers where applicable
Applies the override specifier where applicable. In the case of
destructors that are defaulted in their definition, they can
simply be removed.
This also removes the unnecessary inclusions being done in audin_u and
audrec_u, given their close proximity.
* kernel/thread: Make parameter of GetWaitObjectIndex() const qualified
The pointed to member is never actually modified, so it can be made
const.
* kernel/thread: Avoid sign conversion within GetCommandBufferAddress()
Previously this was performing a u64 + int sign conversion. When dealing
with addresses, we should generally be keeping the arithmetic in the
same signedness type.
This also gets rid of the static lifetime of the constant, as there's no
need to make a trivial type like this potentially live for the entire
duration of the program.
* kernel/codeset: Make CodeSet's memory data member a regular std::vector
The use of a shared_ptr is an implementation detail of the VMManager
itself when mapping memory. Because of that, we shouldn't require all
users of the CodeSet to have to allocate the shared_ptr ahead of time.
It's intended that CodeSet simply pass in the required direct data, and
that the memory manager takes care of it from that point on.
This means we just do the shared pointer allocation in a single place,
when loading modules, as opposed to in each loader.
* kernel/wait_object: Make ShouldWait() take thread members by pointer-to-const
Given this is intended as a querying function, it doesn't make sense to
allow the implementer to modify the state of the given thread.
2019-05-01 14:28:49 +02:00
|
|
|
bool Thread::ShouldWait(const Thread* thread) const {
|
2018-07-20 03:39:05 +02:00
|
|
|
return status != ThreadStatus::Dead;
|
2014-12-22 07:32:03 +01:00
|
|
|
}
|
2014-05-10 04:11:18 +02:00
|
|
|
|
2017-01-01 22:53:22 +01:00
|
|
|
void Thread::Acquire(Thread* thread) {
|
|
|
|
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
|
2015-01-18 04:23:49 +01:00
|
|
|
}
|
|
|
|
|
2018-10-23 16:32:23 +02:00
|
|
|
u32 ThreadManager::NewThreadId() {
|
2015-01-26 07:56:17 +01:00
|
|
|
return next_thread_id++;
|
|
|
|
}
|
2014-12-04 14:13:53 +01:00
|
|
|
|
2018-10-23 15:57:59 +02:00
|
|
|
Thread::Thread(KernelSystem& kernel)
|
2019-02-01 17:23:39 +01:00
|
|
|
: WaitObject(kernel), context(kernel.GetThreadManager().NewContext()),
|
2018-10-23 15:57:59 +02:00
|
|
|
thread_manager(kernel.GetThreadManager()) {}
|
2016-09-19 03:01:46 +02:00
|
|
|
Thread::~Thread() {}
|
2015-01-31 22:22:40 +01:00
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
Thread* ThreadManager::GetCurrentThread() const {
|
2016-12-17 12:08:38 +01:00
|
|
|
return current_thread.get();
|
2014-05-16 00:27:08 +02:00
|
|
|
}
|
2014-05-14 04:00:11 +02:00
|
|
|
|
2015-01-26 07:56:17 +01:00
|
|
|
void Thread::Stop() {
|
|
|
|
// Cancel any outstanding wakeup events for this thread
|
2019-02-01 17:54:52 +01:00
|
|
|
thread_manager.kernel.timing.UnscheduleEvent(thread_manager.ThreadWakeupEventType, thread_id);
|
2018-10-23 17:57:59 +02:00
|
|
|
thread_manager.wakeup_callback_table.erase(thread_id);
|
2015-01-26 07:56:17 +01:00
|
|
|
|
|
|
|
// Clean up thread from ready queue
|
|
|
|
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
|
2018-07-20 03:39:05 +02:00
|
|
|
if (status == ThreadStatus::Ready) {
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager.ready_queue.remove(current_priority, this);
|
2015-01-26 07:56:17 +01:00
|
|
|
}
|
|
|
|
|
2018-07-20 03:39:05 +02:00
|
|
|
status = ThreadStatus::Dead;
|
2015-05-25 20:34:09 +02:00
|
|
|
|
2015-01-21 00:20:47 +01:00
|
|
|
WakeupAllWaitingThreads();
|
2014-06-10 04:14:03 +02:00
|
|
|
|
2015-01-26 07:56:17 +01:00
|
|
|
// Clean up any dangling references in objects that this thread was waiting for
|
2015-02-01 02:26:16 +01:00
|
|
|
for (auto& wait_object : wait_objects) {
|
|
|
|
wait_object->RemoveWaitingThread(this);
|
|
|
|
}
|
2015-07-17 07:24:13 +02:00
|
|
|
wait_objects.clear();
|
2015-05-11 06:19:54 +02:00
|
|
|
|
2017-01-03 01:38:08 +01:00
|
|
|
// Release all the mutexes that this thread holds
|
|
|
|
ReleaseThreadMutexes(this);
|
|
|
|
|
2016-04-20 00:12:48 +02:00
|
|
|
// Mark the TLS slot in the thread's page as free.
|
|
|
|
u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
|
2016-09-18 02:38:01 +02:00
|
|
|
u32 tls_slot =
|
|
|
|
((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
|
2018-10-17 21:23:56 +02:00
|
|
|
owner_process->tls_slots[tls_page].reset(tls_slot);
|
2014-05-14 04:00:11 +02:00
|
|
|
}
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
void ThreadManager::SwitchContext(Thread* new_thread) {
|
2015-01-26 07:56:17 +01:00
|
|
|
Thread* previous_thread = GetCurrentThread();
|
2014-11-19 09:49:13 +01:00
|
|
|
|
2019-02-01 17:54:52 +01:00
|
|
|
Core::Timing& timing = kernel.timing;
|
2018-10-27 21:53:20 +02:00
|
|
|
|
2015-01-26 07:56:17 +01:00
|
|
|
// Save context for previous thread
|
|
|
|
if (previous_thread) {
|
2018-10-27 21:53:20 +02:00
|
|
|
previous_thread->last_running_ticks = timing.GetTicks();
|
2019-02-01 17:23:39 +01:00
|
|
|
cpu->SaveContext(previous_thread->context);
|
2014-11-19 09:49:13 +01:00
|
|
|
|
2018-07-20 03:39:05 +02:00
|
|
|
if (previous_thread->status == ThreadStatus::Running) {
|
2015-01-26 07:56:17 +01:00
|
|
|
// This is only the case when a reschedule is triggered without the current thread
|
|
|
|
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
|
|
|
ready_queue.push_front(previous_thread->current_priority, previous_thread);
|
2018-07-20 03:39:05 +02:00
|
|
|
previous_thread->status = ThreadStatus::Ready;
|
2014-05-21 01:37:46 +02:00
|
|
|
}
|
|
|
|
}
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2014-05-21 01:37:46 +02:00
|
|
|
// Load context of new thread
|
2015-01-26 07:56:17 +01:00
|
|
|
if (new_thread) {
|
2018-07-20 03:39:05 +02:00
|
|
|
ASSERT_MSG(new_thread->status == ThreadStatus::Ready,
|
2017-01-04 18:48:13 +01:00
|
|
|
"Thread must be ready to become running.");
|
2015-05-11 21:09:10 +02:00
|
|
|
|
2015-06-08 05:39:37 +02:00
|
|
|
// Cancel any outstanding wakeup events for this thread
|
2018-10-27 21:53:20 +02:00
|
|
|
timing.UnscheduleEvent(ThreadWakeupEventType, new_thread->thread_id);
|
2015-06-08 05:39:37 +02:00
|
|
|
|
2018-11-21 05:50:00 +01:00
|
|
|
auto previous_process = kernel.GetCurrentProcess();
|
2017-07-23 02:37:26 +02:00
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
current_thread = SharedFrom(new_thread);
|
2015-01-26 07:56:17 +01:00
|
|
|
|
|
|
|
ready_queue.remove(new_thread->current_priority, new_thread);
|
2018-07-20 03:39:05 +02:00
|
|
|
new_thread->status = ThreadStatus::Running;
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
if (previous_process.get() != current_thread->owner_process) {
|
|
|
|
kernel.SetCurrentProcess(SharedFrom(current_thread->owner_process));
|
2017-07-22 04:28:03 +02:00
|
|
|
}
|
2017-09-24 23:42:42 +02:00
|
|
|
|
2019-02-01 17:23:39 +01:00
|
|
|
cpu->LoadContext(new_thread->context);
|
|
|
|
cpu->SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress());
|
2014-05-21 01:37:46 +02:00
|
|
|
} else {
|
2014-12-22 14:07:22 +01:00
|
|
|
current_thread = nullptr;
|
2017-07-22 04:28:03 +02:00
|
|
|
// Note: We do not reset the current process and current page table when idling because
|
|
|
|
// technically we haven't changed processes, our threads are just paused.
|
2014-05-21 01:37:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
Thread* ThreadManager::PopNextReadyThread() {
|
2014-12-22 14:07:22 +01:00
|
|
|
Thread* next;
|
2015-01-26 07:56:17 +01:00
|
|
|
Thread* thread = GetCurrentThread();
|
2014-11-19 09:49:13 +01:00
|
|
|
|
2018-07-20 03:39:05 +02:00
|
|
|
if (thread && thread->status == ThreadStatus::Running) {
|
2015-01-26 07:56:17 +01:00
|
|
|
// 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);
|
2015-05-11 21:09:10 +02:00
|
|
|
if (!next) {
|
|
|
|
// Otherwise just keep going with the current thread
|
|
|
|
next = thread;
|
|
|
|
}
|
2016-09-18 02:38:01 +02:00
|
|
|
} else {
|
2015-01-26 07:56:17 +01:00
|
|
|
next = ready_queue.pop_first();
|
2014-05-21 01:37:46 +02:00
|
|
|
}
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2014-12-22 14:07:22 +01:00
|
|
|
return next;
|
2014-05-21 01:37:46 +02:00
|
|
|
}
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
void ThreadManager::WaitCurrentThread_Sleep() {
|
2015-01-17 08:03:44 +01:00
|
|
|
Thread* thread = GetCurrentThread();
|
2018-07-20 03:39:05 +02:00
|
|
|
thread->status = ThreadStatus::WaitSleep;
|
2015-01-17 08:03:44 +01:00
|
|
|
}
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
void ThreadManager::ExitCurrentThread() {
|
2016-12-17 12:08:38 +01:00
|
|
|
Thread* thread = GetCurrentThread();
|
|
|
|
thread->Stop();
|
2019-03-23 21:04:19 +01:00
|
|
|
thread_list.erase(std::remove_if(thread_list.begin(), thread_list.end(),
|
|
|
|
[thread](const auto& p) { return p.get() == thread; }),
|
2016-12-17 12:08:38 +01:00
|
|
|
thread_list.end());
|
|
|
|
}
|
|
|
|
|
2018-10-23 17:57:59 +02:00
|
|
|
void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) {
|
2019-03-23 21:04:19 +01:00
|
|
|
std::shared_ptr<Thread> thread = SharedFrom(wakeup_callback_table.at(thread_id));
|
2015-01-07 22:40:08 +01:00
|
|
|
if (thread == nullptr) {
|
2018-10-19 04:19:29 +02:00
|
|
|
LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", thread_id);
|
2015-01-07 22:40:08 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-20 03:39:05 +02:00
|
|
|
if (thread->status == ThreadStatus::WaitSynchAny ||
|
|
|
|
thread->status == ThreadStatus::WaitSynchAll || thread->status == ThreadStatus::WaitArb ||
|
|
|
|
thread->status == ThreadStatus::WaitHleEvent) {
|
2017-09-28 18:53:32 +02:00
|
|
|
|
|
|
|
// Invoke the wakeup callback before clearing the wait objects
|
|
|
|
if (thread->wakeup_callback)
|
|
|
|
thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr);
|
|
|
|
|
2016-12-10 19:29:31 +01:00
|
|
|
// Remove the thread from each of its waiting objects' waitlists
|
|
|
|
for (auto& object : thread->wait_objects)
|
|
|
|
object->RemoveWaitingThread(thread.get());
|
|
|
|
thread->wait_objects.clear();
|
2015-01-26 07:56:17 +01:00
|
|
|
}
|
2015-01-17 08:03:44 +01:00
|
|
|
|
2014-12-22 14:07:22 +01:00
|
|
|
thread->ResumeFromWait();
|
2015-01-07 22:40:08 +01:00
|
|
|
}
|
|
|
|
|
2015-01-31 02:07:54 +01:00
|
|
|
void Thread::WakeAfterDelay(s64 nanoseconds) {
|
2015-01-07 22:40:08 +01:00
|
|
|
// Don't schedule a wakeup if the thread wants to wait forever
|
|
|
|
if (nanoseconds == -1)
|
|
|
|
return;
|
|
|
|
|
2019-02-01 17:54:52 +01:00
|
|
|
thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds),
|
|
|
|
thread_manager.ThreadWakeupEventType, thread_id);
|
2015-01-07 22:40:08 +01:00
|
|
|
}
|
|
|
|
|
2014-12-22 14:07:22 +01:00
|
|
|
void Thread::ResumeFromWait() {
|
2017-01-04 18:48:13 +01:00
|
|
|
ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
|
|
|
|
|
2015-01-26 07:56:17 +01:00
|
|
|
switch (status) {
|
2018-07-20 03:39:05 +02:00
|
|
|
case ThreadStatus::WaitSynchAll:
|
|
|
|
case ThreadStatus::WaitSynchAny:
|
|
|
|
case ThreadStatus::WaitHleEvent:
|
|
|
|
case ThreadStatus::WaitArb:
|
|
|
|
case ThreadStatus::WaitSleep:
|
|
|
|
case ThreadStatus::WaitIPC:
|
2016-09-18 02:38:01 +02:00
|
|
|
break;
|
|
|
|
|
2018-07-20 03:39:05 +02:00
|
|
|
case ThreadStatus::Ready:
|
2017-09-28 18:53:32 +02:00
|
|
|
// The thread's wakeup callback must have already been cleared when the thread was first
|
|
|
|
// awoken.
|
|
|
|
ASSERT(wakeup_callback == nullptr);
|
2016-09-18 02:38:01 +02:00
|
|
|
// If the thread is waiting on multiple wait objects, it might be awoken more than once
|
|
|
|
// before actually resuming. We can ignore subsequent wakeups if the thread status has
|
2018-07-20 03:39:05 +02:00
|
|
|
// already been set to ThreadStatus::Ready.
|
2016-09-18 02:38:01 +02:00
|
|
|
return;
|
|
|
|
|
2018-07-20 03:39:05 +02:00
|
|
|
case ThreadStatus::Running:
|
2018-03-27 17:28:42 +02:00
|
|
|
DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId());
|
2016-09-18 02:38:01 +02:00
|
|
|
return;
|
2018-07-20 03:39:05 +02:00
|
|
|
case ThreadStatus::Dead:
|
2016-09-18 02:38:01 +02:00
|
|
|
// This should never happen, as threads must complete before being stopped.
|
2018-03-27 17:28:42 +02:00
|
|
|
DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.",
|
2016-09-18 02:38:01 +02:00
|
|
|
GetObjectId());
|
|
|
|
return;
|
2014-05-21 01:37:46 +02:00
|
|
|
}
|
2015-05-25 20:34:09 +02:00
|
|
|
|
2017-09-28 18:53:32 +02:00
|
|
|
wakeup_callback = nullptr;
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager.ready_queue.push_back(current_priority, this);
|
2018-07-20 03:39:05 +02:00
|
|
|
status = ThreadStatus::Ready;
|
2019-02-01 18:19:20 +01:00
|
|
|
thread_manager.kernel.PrepareReschedule();
|
2014-05-21 01:37:46 +02:00
|
|
|
}
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
void ThreadManager::DebugThreadQueue() {
|
2014-06-06 04:35:36 +02:00
|
|
|
Thread* thread = GetCurrentThread();
|
|
|
|
if (!thread) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD");
|
2015-01-26 07:56:17 +01:00
|
|
|
} else {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_DEBUG(Kernel, "0x{:02X} {} (current)", thread->current_priority,
|
2018-06-29 15:56:12 +02:00
|
|
|
GetCurrentThread()->GetObjectId());
|
2014-06-06 04:35:36 +02:00
|
|
|
}
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2014-12-29 13:55:30 +01:00
|
|
|
for (auto& t : thread_list) {
|
2017-09-27 01:26:09 +02:00
|
|
|
u32 priority = ready_queue.contains(t.get());
|
2014-06-06 04:35:36 +02:00
|
|
|
if (priority != -1) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_DEBUG(Kernel, "0x{:02X} {}", priority, t->GetObjectId());
|
2014-06-06 04:35:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-20 00:12:48 +02:00
|
|
|
/**
|
|
|
|
* Finds a free location for the TLS section of a thread.
|
|
|
|
* @param tls_slots The TLS page array of the thread's owner process.
|
|
|
|
* Returns a tuple of (page, slot, alloc_needed) where:
|
|
|
|
* page: The index of the first allocated TLS page that has free slots.
|
|
|
|
* slot: The index of the first free slot in the indicated page.
|
|
|
|
* alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
|
|
|
|
*/
|
2018-08-02 18:01:22 +02:00
|
|
|
static std::tuple<std::size_t, std::size_t, bool> GetFreeThreadLocalSlot(
|
2018-08-02 17:55:10 +02:00
|
|
|
const std::vector<std::bitset<8>>& tls_slots) {
|
2016-04-20 00:12:48 +02:00
|
|
|
// Iterate over all the allocated pages, and try to find one where not all slots are used.
|
2018-08-02 18:01:22 +02:00
|
|
|
for (std::size_t page = 0; page < tls_slots.size(); ++page) {
|
2016-04-20 00:12:48 +02:00
|
|
|
const auto& page_tls_slots = tls_slots[page];
|
|
|
|
if (!page_tls_slots.all()) {
|
|
|
|
// We found a page with at least one free slot, find which slot it is
|
2018-08-02 18:01:22 +02:00
|
|
|
for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) {
|
2016-04-20 00:12:48 +02:00
|
|
|
if (!page_tls_slots.test(slot)) {
|
|
|
|
return std::make_tuple(page, slot, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_tuple(0, 0, true);
|
|
|
|
}
|
|
|
|
|
2016-09-02 14:53:42 +02:00
|
|
|
/**
|
|
|
|
* Resets a thread context, making it ready to be scheduled and run by the CPU
|
|
|
|
* @param context Thread context to reset
|
|
|
|
* @param stack_top Address of the top of the stack
|
|
|
|
* @param entry_point Address of entry point for execution
|
|
|
|
* @param arg User argument for thread
|
|
|
|
*/
|
2017-12-12 20:12:03 +01:00
|
|
|
static void ResetThreadContext(const std::unique_ptr<ARM_Interface::ThreadContext>& context,
|
|
|
|
u32 stack_top, u32 entry_point, u32 arg) {
|
|
|
|
context->Reset();
|
|
|
|
context->SetCpuRegister(0, arg);
|
|
|
|
context->SetProgramCounter(entry_point);
|
|
|
|
context->SetStackPointer(stack_top);
|
|
|
|
context->SetCpsr(USER32MODE | ((entry_point & 1) << 5)); // Usermode and THUMB mode
|
2016-09-02 14:53:42 +02:00
|
|
|
}
|
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name, VAddr entry_point,
|
|
|
|
u32 priority, u32 arg,
|
|
|
|
s32 processor_id, VAddr stack_top,
|
|
|
|
Process& owner_process) {
|
2017-05-21 09:11:36 +02:00
|
|
|
// Check if priority is in ranged. Lowest priority -> highest priority id.
|
2018-09-21 16:39:10 +02:00
|
|
|
if (priority > ThreadPrioLowest) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
|
2017-05-21 09:11:36 +02:00
|
|
|
return ERR_OUT_OF_RANGE;
|
|
|
|
}
|
|
|
|
|
2018-09-21 16:39:10 +02:00
|
|
|
if (processor_id > ThreadProcessorIdMax) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id);
|
2017-05-21 09:11:36 +02:00
|
|
|
return ERR_OUT_OF_RANGE_KERNEL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(yuriks): Other checks, returning 0xD9001BEA
|
2014-05-17 06:56:00 +02:00
|
|
|
|
2018-11-21 22:51:41 +01:00
|
|
|
if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point);
|
2014-12-22 14:07:22 +01:00
|
|
|
// TODO: Verify error
|
|
|
|
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
|
2016-09-18 02:38:01 +02:00
|
|
|
ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
|
2014-12-22 14:07:22 +01:00
|
|
|
}
|
2014-05-21 03:02:35 +02:00
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
auto thread{std::make_shared<Thread>(*this)};
|
2014-06-06 04:35:36 +02:00
|
|
|
|
2018-10-23 18:18:35 +02:00
|
|
|
thread_manager->thread_list.push_back(thread);
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager->ready_queue.prepare(priority);
|
2014-06-06 04:35:36 +02:00
|
|
|
|
2018-10-23 16:32:23 +02:00
|
|
|
thread->thread_id = thread_manager->NewThreadId();
|
2018-07-20 03:39:05 +02:00
|
|
|
thread->status = ThreadStatus::Dormant;
|
2014-06-10 04:14:03 +02:00
|
|
|
thread->entry_point = entry_point;
|
|
|
|
thread->stack_top = stack_top;
|
2015-03-24 04:55:21 +01:00
|
|
|
thread->nominal_priority = thread->current_priority = priority;
|
2019-02-01 17:54:52 +01:00
|
|
|
thread->last_running_ticks = timing.GetTicks();
|
2014-06-10 04:14:03 +02:00
|
|
|
thread->processor_id = processor_id;
|
2015-01-15 05:41:33 +01:00
|
|
|
thread->wait_objects.clear();
|
2014-12-03 06:46:34 +01:00
|
|
|
thread->wait_address = 0;
|
2014-12-29 13:55:30 +01:00
|
|
|
thread->name = std::move(name);
|
2018-10-23 17:57:59 +02:00
|
|
|
thread_manager->wakeup_callback_table[thread->thread_id] = thread.get();
|
2018-10-26 15:37:46 +02:00
|
|
|
thread->owner_process = &owner_process;
|
2015-05-11 06:19:54 +02:00
|
|
|
|
|
|
|
// Find the next available TLS index, and mark it as used
|
2018-10-26 15:37:46 +02:00
|
|
|
auto& tls_slots = owner_process.tls_slots;
|
2016-04-20 00:12:48 +02:00
|
|
|
|
2018-08-02 18:01:22 +02:00
|
|
|
auto [available_page, available_slot, needs_allocation] = GetFreeThreadLocalSlot(tls_slots);
|
2016-04-20 00:12:48 +02:00
|
|
|
|
|
|
|
if (needs_allocation) {
|
|
|
|
// There are no already-allocated pages with free slots, lets allocate a new one.
|
|
|
|
// TLS pages are allocated from the BASE region in the linear heap.
|
|
|
|
MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE);
|
|
|
|
|
2018-11-06 21:00:47 +01:00
|
|
|
// Allocate some memory from the end of the linear heap for this region.
|
|
|
|
auto offset = memory_region->LinearAllocate(Memory::PAGE_SIZE);
|
|
|
|
if (!offset) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_ERROR(Kernel_SVC,
|
2018-06-29 15:56:12 +02:00
|
|
|
"Not enough space in region to allocate a new TLS page for thread");
|
2017-05-21 09:11:36 +02:00
|
|
|
return ERR_OUT_OF_MEMORY;
|
2015-05-11 06:19:54 +02:00
|
|
|
}
|
2018-11-06 21:00:47 +01:00
|
|
|
owner_process.memory_used += Memory::PAGE_SIZE;
|
2016-04-20 00:12:48 +02:00
|
|
|
|
|
|
|
tls_slots.emplace_back(0); // The page is completely available at the start
|
2018-08-02 18:01:22 +02:00
|
|
|
available_page = tls_slots.size() - 1;
|
2016-04-20 00:12:48 +02:00
|
|
|
available_slot = 0; // Use the first slot in the new page
|
|
|
|
|
2018-10-26 15:37:46 +02:00
|
|
|
auto& vm_manager = owner_process.vm_manager;
|
2016-04-20 00:12:48 +02:00
|
|
|
|
|
|
|
// Map the page to the current process' address space.
|
2018-11-06 21:00:47 +01:00
|
|
|
vm_manager.MapBackingMemory(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
|
2018-12-01 23:46:18 +01:00
|
|
|
memory.GetFCRAMPointer(*offset), Memory::PAGE_SIZE,
|
2018-11-09 06:14:13 +01:00
|
|
|
MemoryState::Locked);
|
2015-05-11 06:19:54 +02:00
|
|
|
}
|
2014-06-06 04:35:36 +02:00
|
|
|
|
2016-04-20 00:12:48 +02:00
|
|
|
// Mark the slot as used
|
|
|
|
tls_slots[available_page].set(available_slot);
|
2016-09-18 02:38:01 +02:00
|
|
|
thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE +
|
|
|
|
available_slot * Memory::TLS_ENTRY_SIZE;
|
2015-05-11 01:43:59 +02:00
|
|
|
|
2018-11-21 21:21:30 +01:00
|
|
|
memory.ZeroBlock(owner_process, thread->tls_address, Memory::TLS_ENTRY_SIZE);
|
2018-11-06 21:00:47 +01:00
|
|
|
|
2015-01-26 07:56:17 +01:00
|
|
|
// TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
|
|
|
|
// to initialize the context
|
2016-09-02 14:53:42 +02:00
|
|
|
ResetThreadContext(thread->context, stack_top, entry_point, arg);
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager->ready_queue.push_back(thread->current_priority, thread.get());
|
2018-07-20 03:39:05 +02:00
|
|
|
thread->status = ThreadStatus::Ready;
|
2014-06-02 03:42:50 +02:00
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
return MakeResult<std::shared_ptr<Thread>>(std::move(thread));
|
2014-06-02 04:12:54 +02:00
|
|
|
}
|
|
|
|
|
2017-09-27 01:26:09 +02:00
|
|
|
void Thread::SetPriority(u32 priority) {
|
2018-09-21 16:39:10 +02:00
|
|
|
ASSERT_MSG(priority <= ThreadPrioLowest && priority >= ThreadPrioHighest,
|
2017-01-11 18:08:10 +01:00
|
|
|
"Invalid priority value.");
|
2015-03-24 04:55:21 +01:00
|
|
|
// If thread was ready, adjust queues
|
2018-07-20 03:39:05 +02:00
|
|
|
if (status == ThreadStatus::Ready)
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager.ready_queue.move(this, current_priority, priority);
|
2015-05-12 02:38:10 +02:00
|
|
|
else
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager.ready_queue.prepare(priority);
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2015-03-24 04:55:21 +01:00
|
|
|
nominal_priority = current_priority = priority;
|
2014-06-02 04:12:54 +02:00
|
|
|
}
|
|
|
|
|
2017-01-03 01:38:08 +01:00
|
|
|
void Thread::UpdatePriority() {
|
2017-09-27 01:26:09 +02:00
|
|
|
u32 best_priority = nominal_priority;
|
2017-01-03 01:38:08 +01:00
|
|
|
for (auto& mutex : held_mutexes) {
|
|
|
|
if (mutex->priority < best_priority)
|
|
|
|
best_priority = mutex->priority;
|
|
|
|
}
|
|
|
|
BoostPriority(best_priority);
|
|
|
|
}
|
|
|
|
|
2017-09-27 01:26:09 +02:00
|
|
|
void Thread::BoostPriority(u32 priority) {
|
2017-01-03 01:38:08 +01:00
|
|
|
// If thread was ready, adjust queues
|
2018-07-20 03:39:05 +02:00
|
|
|
if (status == ThreadStatus::Ready)
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager.ready_queue.move(this, current_priority, priority);
|
2017-01-03 01:38:08 +01:00
|
|
|
else
|
2018-10-23 17:40:57 +02:00
|
|
|
thread_manager.ready_queue.prepare(priority);
|
2015-04-04 00:40:16 +02:00
|
|
|
current_priority = priority;
|
|
|
|
}
|
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority,
|
|
|
|
std::shared_ptr<Process> owner_process) {
|
2014-05-16 00:27:08 +02:00
|
|
|
// Initialize new "main" thread
|
2017-11-06 19:14:12 +01:00
|
|
|
auto thread_res =
|
2018-10-12 21:47:06 +02:00
|
|
|
kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor,
|
2018-10-26 15:37:46 +02:00
|
|
|
Memory::HEAP_VADDR_END, *owner_process);
|
2015-01-26 07:56:17 +01:00
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
|
2014-11-19 09:49:13 +01:00
|
|
|
|
2017-12-12 20:12:03 +01:00
|
|
|
thread->context->SetFpscr(FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO |
|
|
|
|
FPSCR_IXC); // 0x03C00010
|
2016-05-14 15:45:04 +02:00
|
|
|
|
2017-08-22 03:54:29 +02:00
|
|
|
// Note: The newly created thread will be run when the scheduler fires.
|
2014-12-22 14:07:22 +01:00
|
|
|
return thread;
|
2014-05-16 00:27:08 +02:00
|
|
|
}
|
2014-05-15 02:50:30 +02:00
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
bool ThreadManager::HaveReadyThreads() {
|
2017-01-05 20:14:22 +01:00
|
|
|
return ready_queue.get_first() != nullptr;
|
|
|
|
}
|
|
|
|
|
2018-10-23 17:40:57 +02:00
|
|
|
void ThreadManager::Reschedule() {
|
2015-05-11 21:09:10 +02:00
|
|
|
Thread* cur = GetCurrentThread();
|
2015-01-26 07:56:17 +01:00
|
|
|
Thread* next = PopNextReadyThread();
|
2016-03-21 07:48:40 +01:00
|
|
|
|
2015-05-11 21:09:10 +02:00
|
|
|
if (cur && next) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId());
|
2015-05-11 21:09:10 +02:00
|
|
|
} else if (cur) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
|
2015-05-14 17:10:04 +02:00
|
|
|
} else if (next) {
|
2018-06-29 13:18:07 +02:00
|
|
|
LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
|
2014-05-23 00:50:36 +02:00
|
|
|
}
|
2015-05-25 20:34:09 +02:00
|
|
|
|
2015-05-11 21:09:10 +02:00
|
|
|
SwitchContext(next);
|
2014-05-17 06:56:00 +02:00
|
|
|
}
|
|
|
|
|
2015-01-21 02:53:52 +01:00
|
|
|
void Thread::SetWaitSynchronizationResult(ResultCode result) {
|
2017-12-12 20:12:03 +01:00
|
|
|
context->SetCpuRegister(0, result.raw);
|
2015-01-21 02:53:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Thread::SetWaitSynchronizationOutput(s32 output) {
|
2017-12-12 20:12:03 +01:00
|
|
|
context->SetCpuRegister(1, output);
|
2015-01-17 08:03:44 +01:00
|
|
|
}
|
|
|
|
|
Port various minor changes from yuzu PRs (#4725)
* common/thread: Remove unused functions
Many of these functions are carried over from Dolphin (where they aren't
used anymore). Given these have no use (and we really shouldn't be
screwing around with OS-specific thread scheduler handling from the
emulator, these can be removed.
The function for setting the thread name is left, however, since it can
have debugging utility usages.
* input_common/sdl: Use a type alias to shorten declaration of GetPollers
Just makes the definitions a little bit more tidy.
* input_common/sdl: Correct return values within implementations of GetPollers()
In both cases, we weren't actually returning anything, which is
undefined behavior.
* yuzu/debugger/graphics_surface: Fill in missing surface format listings
Fills in the missing surface types that were marked as unknown. The
order corresponds with the TextureFormat enum within
video_core/texture.h.
We also don't need to all of these strings as translatable (only the
first string, as it's an English word).
* yuzu/debugger/graphics_surface: Clean up connection overload deduction
We can utilize qOverload with the signal connections to make the
function deducing a little less ugly.
* yuzu/debugger/graphics_surface: Tidy up SaveSurface
- Use QStringLiteral where applicable.
- Use const where applicable
- Remove unnecessary precondition check (we already assert the pixbuf
being non null)
* yuzu/debugger/graphics_surface: Display error messages for file I/O errors
* core: Add missing override specifiers where applicable
Applies the override specifier where applicable. In the case of
destructors that are defaulted in their definition, they can
simply be removed.
This also removes the unnecessary inclusions being done in audin_u and
audrec_u, given their close proximity.
* kernel/thread: Make parameter of GetWaitObjectIndex() const qualified
The pointed to member is never actually modified, so it can be made
const.
* kernel/thread: Avoid sign conversion within GetCommandBufferAddress()
Previously this was performing a u64 + int sign conversion. When dealing
with addresses, we should generally be keeping the arithmetic in the
same signedness type.
This also gets rid of the static lifetime of the constant, as there's no
need to make a trivial type like this potentially live for the entire
duration of the program.
* kernel/codeset: Make CodeSet's memory data member a regular std::vector
The use of a shared_ptr is an implementation detail of the VMManager
itself when mapping memory. Because of that, we shouldn't require all
users of the CodeSet to have to allocate the shared_ptr ahead of time.
It's intended that CodeSet simply pass in the required direct data, and
that the memory manager takes care of it from that point on.
This means we just do the shared pointer allocation in a single place,
when loading modules, as opposed to in each loader.
* kernel/wait_object: Make ShouldWait() take thread members by pointer-to-const
Given this is intended as a querying function, it doesn't make sense to
allow the implementer to modify the state of the given thread.
2019-05-01 14:28:49 +02:00
|
|
|
s32 Thread::GetWaitObjectIndex(const WaitObject* object) const {
|
2017-01-04 18:48:13 +01:00
|
|
|
ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything");
|
Port various minor changes from yuzu PRs (#4725)
* common/thread: Remove unused functions
Many of these functions are carried over from Dolphin (where they aren't
used anymore). Given these have no use (and we really shouldn't be
screwing around with OS-specific thread scheduler handling from the
emulator, these can be removed.
The function for setting the thread name is left, however, since it can
have debugging utility usages.
* input_common/sdl: Use a type alias to shorten declaration of GetPollers
Just makes the definitions a little bit more tidy.
* input_common/sdl: Correct return values within implementations of GetPollers()
In both cases, we weren't actually returning anything, which is
undefined behavior.
* yuzu/debugger/graphics_surface: Fill in missing surface format listings
Fills in the missing surface types that were marked as unknown. The
order corresponds with the TextureFormat enum within
video_core/texture.h.
We also don't need to all of these strings as translatable (only the
first string, as it's an English word).
* yuzu/debugger/graphics_surface: Clean up connection overload deduction
We can utilize qOverload with the signal connections to make the
function deducing a little less ugly.
* yuzu/debugger/graphics_surface: Tidy up SaveSurface
- Use QStringLiteral where applicable.
- Use const where applicable
- Remove unnecessary precondition check (we already assert the pixbuf
being non null)
* yuzu/debugger/graphics_surface: Display error messages for file I/O errors
* core: Add missing override specifiers where applicable
Applies the override specifier where applicable. In the case of
destructors that are defaulted in their definition, they can
simply be removed.
This also removes the unnecessary inclusions being done in audin_u and
audrec_u, given their close proximity.
* kernel/thread: Make parameter of GetWaitObjectIndex() const qualified
The pointed to member is never actually modified, so it can be made
const.
* kernel/thread: Avoid sign conversion within GetCommandBufferAddress()
Previously this was performing a u64 + int sign conversion. When dealing
with addresses, we should generally be keeping the arithmetic in the
same signedness type.
This also gets rid of the static lifetime of the constant, as there's no
need to make a trivial type like this potentially live for the entire
duration of the program.
* kernel/codeset: Make CodeSet's memory data member a regular std::vector
The use of a shared_ptr is an implementation detail of the VMManager
itself when mapping memory. Because of that, we shouldn't require all
users of the CodeSet to have to allocate the shared_ptr ahead of time.
It's intended that CodeSet simply pass in the required direct data, and
that the memory manager takes care of it from that point on.
This means we just do the shared pointer allocation in a single place,
when loading modules, as opposed to in each loader.
* kernel/wait_object: Make ShouldWait() take thread members by pointer-to-const
Given this is intended as a querying function, it doesn't make sense to
allow the implementer to modify the state of the given thread.
2019-05-01 14:28:49 +02:00
|
|
|
const auto match = std::find_if(wait_objects.rbegin(), wait_objects.rend(),
|
|
|
|
[object](const auto& p) { return p.get() == object; });
|
2017-09-27 01:26:09 +02:00
|
|
|
return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1);
|
2017-01-04 16:53:01 +01:00
|
|
|
}
|
|
|
|
|
2017-09-29 21:47:52 +02:00
|
|
|
VAddr Thread::GetCommandBufferAddress() const {
|
|
|
|
// Offset from the start of TLS at which the IPC command buffer begins.
|
Port various minor changes from yuzu PRs (#4725)
* common/thread: Remove unused functions
Many of these functions are carried over from Dolphin (where they aren't
used anymore). Given these have no use (and we really shouldn't be
screwing around with OS-specific thread scheduler handling from the
emulator, these can be removed.
The function for setting the thread name is left, however, since it can
have debugging utility usages.
* input_common/sdl: Use a type alias to shorten declaration of GetPollers
Just makes the definitions a little bit more tidy.
* input_common/sdl: Correct return values within implementations of GetPollers()
In both cases, we weren't actually returning anything, which is
undefined behavior.
* yuzu/debugger/graphics_surface: Fill in missing surface format listings
Fills in the missing surface types that were marked as unknown. The
order corresponds with the TextureFormat enum within
video_core/texture.h.
We also don't need to all of these strings as translatable (only the
first string, as it's an English word).
* yuzu/debugger/graphics_surface: Clean up connection overload deduction
We can utilize qOverload with the signal connections to make the
function deducing a little less ugly.
* yuzu/debugger/graphics_surface: Tidy up SaveSurface
- Use QStringLiteral where applicable.
- Use const where applicable
- Remove unnecessary precondition check (we already assert the pixbuf
being non null)
* yuzu/debugger/graphics_surface: Display error messages for file I/O errors
* core: Add missing override specifiers where applicable
Applies the override specifier where applicable. In the case of
destructors that are defaulted in their definition, they can
simply be removed.
This also removes the unnecessary inclusions being done in audin_u and
audrec_u, given their close proximity.
* kernel/thread: Make parameter of GetWaitObjectIndex() const qualified
The pointed to member is never actually modified, so it can be made
const.
* kernel/thread: Avoid sign conversion within GetCommandBufferAddress()
Previously this was performing a u64 + int sign conversion. When dealing
with addresses, we should generally be keeping the arithmetic in the
same signedness type.
This also gets rid of the static lifetime of the constant, as there's no
need to make a trivial type like this potentially live for the entire
duration of the program.
* kernel/codeset: Make CodeSet's memory data member a regular std::vector
The use of a shared_ptr is an implementation detail of the VMManager
itself when mapping memory. Because of that, we shouldn't require all
users of the CodeSet to have to allocate the shared_ptr ahead of time.
It's intended that CodeSet simply pass in the required direct data, and
that the memory manager takes care of it from that point on.
This means we just do the shared pointer allocation in a single place,
when loading modules, as opposed to in each loader.
* kernel/wait_object: Make ShouldWait() take thread members by pointer-to-const
Given this is intended as a querying function, it doesn't make sense to
allow the implementer to modify the state of the given thread.
2019-05-01 14:28:49 +02:00
|
|
|
constexpr u32 command_header_offset = 0x80;
|
|
|
|
return GetTLSAddress() + command_header_offset;
|
2017-09-29 21:47:52 +02:00
|
|
|
}
|
|
|
|
|
2018-11-21 05:50:00 +01:00
|
|
|
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel) : kernel(kernel) {
|
2019-02-01 17:54:52 +01:00
|
|
|
ThreadWakeupEventType =
|
|
|
|
kernel.timing.RegisterEvent("ThreadWakeupCallback", [this](u64 thread_id, s64 cycle_late) {
|
|
|
|
ThreadWakeupCallback(thread_id, cycle_late);
|
|
|
|
});
|
2014-05-14 04:00:11 +02:00
|
|
|
}
|
2014-05-10 04:11:18 +02:00
|
|
|
|
2018-10-23 18:18:35 +02:00
|
|
|
ThreadManager::~ThreadManager() {
|
2015-07-17 07:24:13 +02:00
|
|
|
for (auto& t : thread_list) {
|
|
|
|
t->Stop();
|
|
|
|
}
|
2014-05-14 04:00:11 +02:00
|
|
|
}
|
2014-05-21 01:37:46 +02:00
|
|
|
|
2019-03-23 21:04:19 +01:00
|
|
|
const std::vector<std::shared_ptr<Thread>>& ThreadManager::GetThreadList() {
|
2016-04-08 18:28:54 +02:00
|
|
|
return thread_list;
|
|
|
|
}
|
|
|
|
|
2017-11-06 19:14:12 +01:00
|
|
|
} // namespace Kernel
|