mirror of
https://github.com/decaf-emu/decaf-emu.git
synced 2026-10-10 15:48:08 +00:00
432 lines
9.7 KiB
C++
Executable File
432 lines
9.7 KiB
C++
Executable File
#include <algorithm>
|
|
#include "platform.h"
|
|
#include "interpreter.h"
|
|
#include "log.h"
|
|
#include "processor.h"
|
|
#include "ppc.h"
|
|
#include "modules/coreinit/coreinit_core.h"
|
|
#include "modules/coreinit/coreinit_thread.h"
|
|
#include "modules/coreinit/coreinit_scheduler.h"
|
|
#include "ppcinvoke.h"
|
|
#include "debugcontrol.h"
|
|
|
|
Processor
|
|
gProcessor { CoreCount };
|
|
|
|
__declspec(thread) Core *
|
|
tCurrentCore = nullptr;
|
|
|
|
void
|
|
Fiber::fiberEntryPoint(void *param)
|
|
{
|
|
gProcessor.fiberEntryPoint(reinterpret_cast<Fiber*>(param));
|
|
}
|
|
|
|
Processor::Processor(size_t cores)
|
|
{
|
|
for (auto i = 0u; i < cores; ++i) {
|
|
mCores.push_back(new Core { i });
|
|
}
|
|
}
|
|
|
|
// Starts up the CPU threads and Timer thread
|
|
void
|
|
Processor::start()
|
|
{
|
|
mRunning = true;
|
|
|
|
for (auto core : mCores) {
|
|
core->thread = std::thread(std::bind(&Processor::coreEntryPoint, this, core));
|
|
|
|
static const std::string coreNames[] = { "Core #0", "Core #1", "Core #2" };
|
|
platform::set_thread_name(&core->thread, coreNames[core->id]);
|
|
}
|
|
|
|
mTimerThread = std::thread(std::bind(&Processor::timerEntryPoint, this));
|
|
platform::set_thread_name(&mTimerThread, "Timer Thread");
|
|
}
|
|
|
|
void
|
|
Processor::wakeAllCores()
|
|
{
|
|
mCondition.notify_all();
|
|
}
|
|
|
|
// Wait for all threads to end
|
|
void
|
|
Processor::join()
|
|
{
|
|
for (auto core : mCores) {
|
|
core->thread.join();
|
|
}
|
|
|
|
mTimerThread.join();
|
|
}
|
|
|
|
// Entry point of new fibers
|
|
void
|
|
Processor::fiberEntryPoint(Fiber *fiber)
|
|
{
|
|
gInterpreter.executeSub(&fiber->state);
|
|
OSExitThread(ppctypes::getResult<int>(&fiber->state));
|
|
}
|
|
|
|
// Entry point of CPU Core threads
|
|
void
|
|
Processor::coreEntryPoint(Core *core)
|
|
{
|
|
tCurrentCore = core;
|
|
|
|
platform::ui::initialiseCore(core->id);
|
|
|
|
core->primaryFiber = ConvertThreadToFiber(NULL);
|
|
|
|
while (mRunning) {
|
|
// Intentionally do this before the lock...
|
|
gDebugControl.maybeBreak(0, nullptr, core->id);
|
|
|
|
std::unique_lock<std::mutex> lock { mMutex };
|
|
|
|
// Free any fibers which need to be deleted
|
|
for (auto fiber : core->mFiberDeleteList) {
|
|
delete fiber;
|
|
}
|
|
|
|
core->mFiberDeleteList.clear();
|
|
|
|
if (auto fiber = peekNextFiberNoLock(core->id)) {
|
|
// Remove fiber from schedule queue
|
|
mFiberQueue.erase(std::remove(mFiberQueue.begin(), mFiberQueue.end(), fiber), mFiberQueue.end());
|
|
|
|
// Switch to fiber
|
|
core->currentFiber = fiber;
|
|
fiber->coreID = core->id;
|
|
fiber->parentFiber = core->primaryFiber;
|
|
fiber->thread->state = OSThreadState::Running;
|
|
lock.unlock();
|
|
|
|
gLog->trace("Core {} enter thread {}", core->id, fiber->thread->id);
|
|
SwitchToFiber(fiber->handle);
|
|
} else if (core->interrupt) {
|
|
// Switch to the interrupt thread for any waiting interrupts
|
|
lock.unlock();
|
|
handleInterrupt();
|
|
} else {
|
|
// Wait for a valid fiber
|
|
gLog->trace("Core {} wait for thread", core->id);
|
|
mCondition.wait(lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Processor::reschedule(bool hasSchedulerLock, bool yield)
|
|
{
|
|
std::unique_lock<std::mutex> lock { mMutex };
|
|
auto core = tCurrentCore;
|
|
|
|
if (!core) {
|
|
// Ran from host thread
|
|
return;
|
|
}
|
|
|
|
auto fiber = core->currentFiber;
|
|
auto thread = fiber->thread;
|
|
auto next = peekNextFiberNoLock(core->id);
|
|
|
|
// Priority is 0 = highest, 31 = lowest
|
|
if (thread->suspendCounter <= 0 && thread->state == OSThreadState::Running) {
|
|
if (!next) {
|
|
// There is no thread to reschedule to
|
|
return;
|
|
}
|
|
|
|
if (yield) {
|
|
// Yield will transfer control to threads with equal or better priority
|
|
if (thread->basePriority < next->thread->basePriority) {
|
|
return;
|
|
}
|
|
} else {
|
|
// Only reschedule to more important threads
|
|
if (thread->basePriority <= next->thread->basePriority) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Change state to ready, only if this thread is running
|
|
if (fiber->thread->state == OSThreadState::Running) {
|
|
fiber->thread->state = OSThreadState::Ready;
|
|
}
|
|
|
|
// Add this fiber to queue
|
|
queueNoLock(fiber);
|
|
|
|
if (hasSchedulerLock) {
|
|
OSUnlockScheduler();
|
|
}
|
|
|
|
gLog->trace("Core {} leave thread {}", core->id, fiber->thread->id);
|
|
|
|
// Return to main scheduler fiber
|
|
lock.unlock();
|
|
SwitchToFiber(core->primaryFiber);
|
|
|
|
// Reacquire scheduler lock if needed
|
|
if (hasSchedulerLock) {
|
|
OSLockScheduler();
|
|
}
|
|
}
|
|
|
|
// Yield current thread to one of equal or higher priority
|
|
void
|
|
Processor::yield()
|
|
{
|
|
reschedule(false, true);
|
|
}
|
|
|
|
// Exit current thread
|
|
void
|
|
Processor::exit()
|
|
{
|
|
auto core = tCurrentCore;
|
|
auto fiber = core->currentFiber;
|
|
auto parent = fiber->parentFiber;
|
|
auto id = fiber->thread->id;
|
|
|
|
// Destroy current fiber
|
|
gLog->trace("Core {} destroy fiber {}", core->id, id);
|
|
destroyFiber(fiber);
|
|
core->currentFiber = nullptr;
|
|
|
|
// Return to parent fiber
|
|
gLog->trace("Core {} exit thread {}", core->id, id);
|
|
SwitchToFiber(parent);
|
|
}
|
|
|
|
// Insert a fiber into the run queue
|
|
void
|
|
Processor::queue(Fiber *fiber)
|
|
{
|
|
std::unique_lock<std::mutex> lock { mMutex };
|
|
queueNoLock(fiber);
|
|
}
|
|
|
|
void
|
|
Processor::queueNoLock(Fiber *fiber)
|
|
{
|
|
auto compare =
|
|
[](Fiber *lhs, Fiber *rhs) {
|
|
return lhs->thread->basePriority < rhs->thread->basePriority;
|
|
};
|
|
|
|
auto pos = std::upper_bound(mFiberQueue.begin(), mFiberQueue.end(), fiber, compare);
|
|
mFiberQueue.insert(pos, fiber);
|
|
mCondition.notify_all();
|
|
}
|
|
|
|
// Create a new fiber
|
|
Fiber *
|
|
Processor::createFiber()
|
|
{
|
|
std::lock_guard<std::mutex> lock { mMutex };
|
|
return createFiberNoLock();
|
|
}
|
|
|
|
Fiber *
|
|
Processor::createFiberNoLock()
|
|
{
|
|
auto fiber = new Fiber();
|
|
mFiberList.push_back(fiber);
|
|
return fiber;
|
|
}
|
|
|
|
// Add a fiber to the destroy list
|
|
void
|
|
Processor::destroyFiber(Fiber *fiber)
|
|
{
|
|
std::lock_guard<std::mutex> lock { mMutex };
|
|
destroyFiberNoLock(fiber);
|
|
}
|
|
|
|
void
|
|
Processor::destroyFiberNoLock(Fiber *fiber)
|
|
{
|
|
auto core = tCurrentCore;
|
|
mFiberList.erase(std::remove(mFiberList.begin(), mFiberList.end(), fiber), mFiberList.end());
|
|
core->mFiberDeleteList.push_back(fiber);
|
|
}
|
|
|
|
// Find the next suitable fiber to run on a core
|
|
Fiber *
|
|
Processor::peekNextFiberNoLock(uint32_t core)
|
|
{
|
|
auto bit = 1 << core;
|
|
|
|
for (auto fiber : mFiberQueue) {
|
|
if (fiber->thread->state != OSThreadState::Ready) {
|
|
continue;
|
|
}
|
|
|
|
if (fiber->thread->suspendCounter > 0) {
|
|
continue;
|
|
}
|
|
|
|
if (fiber->thread->attr & bit) {
|
|
return fiber;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
uint32_t
|
|
Processor::getCoreID()
|
|
{
|
|
return tCurrentCore ? tCurrentCore->id : 4;
|
|
}
|
|
|
|
uint32_t
|
|
Processor::getCoreCount()
|
|
{
|
|
return static_cast<uint32_t>(mCores.size());
|
|
}
|
|
|
|
Fiber *
|
|
Processor::getCurrentFiber()
|
|
{
|
|
return tCurrentCore ? tCurrentCore->currentFiber : nullptr;
|
|
}
|
|
|
|
OSContext *
|
|
Processor::getInterruptContext()
|
|
{
|
|
if (!tCurrentCore || !tCurrentCore->currentFiber) {
|
|
return nullptr;
|
|
} else {
|
|
return &tCurrentCore->currentFiber->thread->context;
|
|
}
|
|
}
|
|
|
|
// Entry point of interrupt thread
|
|
void
|
|
Processor::timerEntryPoint()
|
|
{
|
|
while (mRunning) {
|
|
std::unique_lock<std::mutex> lock { mTimerMutex };
|
|
auto now = std::chrono::system_clock::now();
|
|
auto next = std::chrono::time_point<std::chrono::system_clock>::max();
|
|
bool timedWait = false;
|
|
|
|
for (auto core : mCores) {
|
|
if (core->nextInterrupt <= now) {
|
|
core->interrupt = true;
|
|
core->nextInterrupt = std::chrono::time_point<std::chrono::system_clock>::max();
|
|
wakeAllCores();
|
|
} else if (core->nextInterrupt < next) {
|
|
next = core->nextInterrupt;
|
|
timedWait = true;
|
|
}
|
|
}
|
|
|
|
if (timedWait) {
|
|
mTimerCondition.wait_until(lock, next);
|
|
} else {
|
|
mTimerCondition.wait(lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sleep the interrupt thread until the first interrupt happens
|
|
void
|
|
Processor::waitFirstInterrupt()
|
|
{
|
|
auto core = tCurrentCore;
|
|
auto fiber = core->currentFiber;
|
|
core->interruptHandlerFiber = fiber;
|
|
SwitchToFiber(core->primaryFiber);
|
|
}
|
|
|
|
// Yield to interrupt thread to handle any pending interrupt
|
|
void
|
|
Processor::handleInterrupt()
|
|
{
|
|
auto core = tCurrentCore;
|
|
|
|
if (core->interrupt) {
|
|
if (core->currentFiber) {
|
|
core->interruptedFiber = core->currentFiber;
|
|
} else {
|
|
core->interruptedFiber = nullptr;
|
|
}
|
|
|
|
core->interrupt = false;
|
|
core->currentFiber = core->interruptHandlerFiber;
|
|
SwitchToFiber(core->currentFiber->handle);
|
|
}
|
|
}
|
|
|
|
// Return to the interrupted thread
|
|
void
|
|
Processor::finishInterrupt()
|
|
{
|
|
auto core = tCurrentCore;
|
|
auto fiber = core->interruptedFiber;
|
|
|
|
core->currentFiber = fiber;
|
|
core->interruptedFiber = nullptr;
|
|
gLog->trace("Exit interrupt core {}", core->id);
|
|
|
|
if (!fiber) {
|
|
SwitchToFiber(core->primaryFiber);
|
|
} else {
|
|
SwitchToFiber(fiber->handle);
|
|
}
|
|
}
|
|
|
|
// Set the interrupt flag for a specific core
|
|
void
|
|
Processor::setInterrupt(uint32_t core)
|
|
{
|
|
std::unique_lock<std::mutex> lock { mTimerMutex };
|
|
mCores[core]->nextInterrupt = std::chrono::time_point<std::chrono::system_clock>::max();
|
|
mCores[core]->interrupt = true;
|
|
}
|
|
|
|
// Set the time of the next interrupt, will not overwrite sooner times
|
|
void
|
|
Processor::setInterruptTimer(uint32_t core, std::chrono::time_point<std::chrono::system_clock> when)
|
|
{
|
|
std::unique_lock<std::mutex> lock { mTimerMutex };
|
|
|
|
if (when < mCores[core]->nextInterrupt) {
|
|
mCores[core]->nextInterrupt = when;
|
|
}
|
|
|
|
mTimerCondition.notify_all();
|
|
}
|
|
|
|
namespace spdlog
|
|
{
|
|
namespace details
|
|
{
|
|
namespace os
|
|
{
|
|
size_t thread_id()
|
|
{
|
|
size_t coreID = 0, threadID = 0;
|
|
|
|
if (tCurrentCore) {
|
|
coreID = tCurrentCore->id;
|
|
|
|
if (tCurrentCore->currentFiber) {
|
|
threadID = tCurrentCore->currentFiber->thread->id;
|
|
}
|
|
}
|
|
|
|
return (coreID << 8) | threadID;
|
|
}
|
|
}
|
|
}
|
|
}
|