mirror of
https://github.com/zeldaret/oot.git
synced 2024-12-02 07:46:01 +00:00
08c8126ba5
* Enable int-conversion warnings for gcc/clang * Fix all current int-conversion warnings * Run format.sh * Apply review suggestions
665 lines
24 KiB
C
665 lines
24 KiB
C
/**
|
|
* @file sched.c
|
|
*
|
|
* This file implements a cooperative scheduler for managing tasks that run on the RSP and RDP
|
|
* asynchronously such as graphics and audio processing. Tasks are prepared and sent to it from
|
|
* other threads, where it is placed in a queue until the necessary resources are available. Tasks
|
|
* are usually ran in the order they are received, with one exception described below. Tasks can
|
|
* also request, through flags, whether the scheduler should swap the active framebuffer once the
|
|
* task completes.
|
|
* Together with irqmgr.c, these systems implement the libultra video and task scheduling model from
|
|
* the libultra "sched" module. Notably, the original sched module supports a wider range of ways to
|
|
* communicate with the RDP, while the Zelda 64 implementation only allows the RSP microcode to send
|
|
* commands to the RDP. The Zelda 64 implementation also has more complex behavior involving the
|
|
* framebuffers.
|
|
*
|
|
* There are four task types supported:
|
|
*
|
|
* M_NULTASK
|
|
* "NULL" tasks.
|
|
* Tasks of this type don't perform any operations, it can be used to "flush" the task queue. Threads
|
|
* can wait for this task to complete to ensure there are no more tasks queued in the scheduler.
|
|
*
|
|
* M_GFXTASK
|
|
* Graphics Processing tasks.
|
|
* Only these tasks can make use of the RDP.
|
|
*
|
|
* M_AUDTASK
|
|
* Audio Processing tasks.
|
|
* These tasks have a higher "priority" than other tasks. If an audio task is enqueued and another
|
|
* task is currently running, the scheduler will signal to the running task that it should "yield"
|
|
* the RSP to the audio task. The running task will save its current state and stop running, allowing
|
|
* the scheduler to send the audio task. This ensures that audio data is always available to be consumed
|
|
* by the audio DAC even if another task such as graphics is running slow, avoiding undesirable sound
|
|
* artifacts. This is the meaning of "cooperative" scheduler, the current task must acknowledge the
|
|
* yield request rather than be immediately interrupted as it would be in a preemptive scheduler.
|
|
*
|
|
* M_NJPEGTASK
|
|
* JPEG to RGBA16 decoding tasks.
|
|
*
|
|
* @see irqmgr.c
|
|
*/
|
|
#include "global.h"
|
|
|
|
#define RSP_DONE_MSG 667
|
|
#define RDP_DONE_MSG 668
|
|
#define NOTIFY_MSG 670 // original name: ENTRY_MSG
|
|
|
|
vs32 sLogScheduler = false;
|
|
|
|
OSTime sRSPGFXStartTime;
|
|
OSTime sRSPAudioStartTime;
|
|
OSTime sRSPOtherStartTime;
|
|
OSTime sRDPStartTime;
|
|
|
|
/**
|
|
* Set the current framebuffer to the swapbuffer pointed to by the provided cfb
|
|
*/
|
|
void Sched_SwapFrameBufferImpl(CfbInfo* cfbInfo) {
|
|
u16 width;
|
|
|
|
LogUtils_CheckValidPointer("cfbinfo->swapbuffer", cfbInfo->swapBuffer, "../sched.c", 340);
|
|
|
|
if (cfbInfo->swapBuffer != NULL) {
|
|
// Register the swapbuffer to display on next VI
|
|
osViSwapBuffer(cfbInfo->swapBuffer);
|
|
cfbInfo->updateTimer = cfbInfo->updateRate;
|
|
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("osViSwapBuffer %08x %08x %08x\n", osViGetCurrentFramebuffer(), osViGetNextFramebuffer(),
|
|
(cfbInfo != NULL) ? cfbInfo->swapBuffer : NULL);
|
|
}
|
|
|
|
width = (cfbInfo->viMode != NULL) ? cfbInfo->viMode->comRegs.width : (u32)gScreenWidth;
|
|
Fault_SetFrameBuffer(cfbInfo->swapBuffer, width, 16);
|
|
|
|
if (HREG(80) == 0xD && HREG(95) != 0xD) {
|
|
HREG(81) = 0;
|
|
HREG(82) = 0;
|
|
HREG(83) = 1;
|
|
HREG(84) = 0;
|
|
HREG(85) = 1;
|
|
HREG(86) = 0;
|
|
HREG(87) = 0;
|
|
HREG(88) = 0;
|
|
HREG(89) = 0;
|
|
HREG(90) = 0;
|
|
HREG(91) = 0;
|
|
HREG(92) = 0;
|
|
HREG(93) = 0;
|
|
HREG(94) = 0;
|
|
HREG(95) = 0xD;
|
|
}
|
|
if (HREG(80) == 0xD && HREG(81) == 2) {
|
|
osViSetSpecialFeatures((HREG(82) != 0) ? OS_VI_GAMMA_ON : OS_VI_GAMMA_OFF);
|
|
osViSetSpecialFeatures((HREG(83) != 0) ? OS_VI_DITHER_FILTER_ON : OS_VI_DITHER_FILTER_OFF);
|
|
osViSetSpecialFeatures((HREG(84) != 0) ? OS_VI_GAMMA_DITHER_ON : OS_VI_GAMMA_DITHER_OFF);
|
|
osViSetSpecialFeatures((HREG(85) != 0) ? OS_VI_DIVOT_ON : OS_VI_DIVOT_OFF);
|
|
}
|
|
}
|
|
cfbInfo->unk_10 = 0;
|
|
}
|
|
|
|
void Sched_SwapFrameBuffer(Scheduler* sc, CfbInfo* cfbInfo) {
|
|
if (sc->isFirstSwap) {
|
|
sc->isFirstSwap = false;
|
|
|
|
if (gIrqMgrResetStatus == IRQ_RESET_STATUS_IDLE) {
|
|
ViConfig_UpdateVi(false);
|
|
}
|
|
}
|
|
Sched_SwapFrameBufferImpl(cfbInfo);
|
|
}
|
|
|
|
void Sched_HandlePreNMI(Scheduler* sc) {
|
|
OSTime now;
|
|
|
|
if (sc->curRSPTask != NULL) {
|
|
now = osGetTime();
|
|
|
|
if (sc->curRSPTask->framebuffer == NULL) {
|
|
// audio and jpeg tasks end up in here
|
|
LOG_TIME("(((u64)(now - audio_rsp_start_time)*(1000000LL/15625LL))/((62500000LL*3/4)/15625LL))",
|
|
OS_CYCLES_TO_USEC(now - sRSPAudioStartTime), "../sched.c", 421);
|
|
} else if (OS_CYCLES_TO_USEC(now - sRSPGFXStartTime) > 1000000 ||
|
|
OS_CYCLES_TO_USEC(now - sRDPStartTime) > 1000000) {
|
|
// More than 1 second since the RSP or RDP tasks began, halt the RSP and RDP
|
|
RcpUtils_Reset();
|
|
// Manually send RSP/RDP done messages to the scheduler interrupt queue if appropriate
|
|
if (sc->curRSPTask != NULL) {
|
|
LOG_TIME("(((u64)(now - graph_rsp_start_time)*(1000000LL/15625LL))/((62500000LL*3/4)/15625LL))",
|
|
OS_CYCLES_TO_USEC(now - sRSPGFXStartTime), "../sched.c", 427);
|
|
osSendMesg(&sc->interruptQueue, (OSMesg)RSP_DONE_MSG, OS_MESG_NOBLOCK);
|
|
}
|
|
if (sc->curRDPTask != NULL) {
|
|
LOG_TIME("(((u64)(now - rdp_start_time)*(1000000LL/15625LL))/((62500000LL*3/4)/15625LL))",
|
|
OS_CYCLES_TO_USEC(now - sRDPStartTime), "../sched.c", 431);
|
|
osSendMesg(&sc->interruptQueue, (OSMesg)RDP_DONE_MSG, OS_MESG_NOBLOCK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sched_HandleNMI(Scheduler* sc) {
|
|
// black the screen and reset the VI y scale just in time for NMI reset
|
|
ViConfig_UpdateVi(true);
|
|
}
|
|
|
|
/**
|
|
* Enqueue a task to either the audio task list or the gfx task list
|
|
*/
|
|
void Sched_QueueTask(Scheduler* sc, OSScTask* task) {
|
|
s32 type = task->list.t.type;
|
|
|
|
ASSERT((type == M_AUDTASK) || (type == M_GFXTASK) || (type == M_NJPEGTASK) || (type == M_NULTASK),
|
|
"(type == M_AUDTASK) || (type == M_GFXTASK) || (type == M_NJPEGTASK) || (type == M_NULTASK)", "../sched.c",
|
|
463);
|
|
|
|
if (type == M_AUDTASK) {
|
|
if (sLogScheduler) {
|
|
// "You have entered an audio task"
|
|
osSyncPrintf("オーディオタスクをエントリしました\n");
|
|
}
|
|
// Add to audio queue
|
|
if (sc->audioListTail != NULL) {
|
|
sc->audioListTail->next = task;
|
|
} else {
|
|
sc->audioListHead = task;
|
|
}
|
|
sc->audioListTail = task;
|
|
|
|
// Set audio flag
|
|
sc->doAudio = true;
|
|
} else {
|
|
if (sLogScheduler) {
|
|
// "Entered graph task"
|
|
osSyncPrintf("グラフタスクをエントリしました\n");
|
|
}
|
|
// Add to graphics queue
|
|
if (sc->gfxListTail != NULL) {
|
|
sc->gfxListTail->next = task;
|
|
} else {
|
|
sc->gfxListHead = task;
|
|
}
|
|
sc->gfxListTail = task;
|
|
}
|
|
task->next = NULL;
|
|
task->state = task->flags & OS_SC_RCP_MASK;
|
|
}
|
|
|
|
void Sched_Yield(Scheduler* sc) {
|
|
if (!(sc->curRSPTask->state & OS_SC_YIELD)) {
|
|
// Not already been asked to yield
|
|
ASSERT(sc->curRSPTask->list.t.type != M_AUDTASK, "sc->curRSPTask->list.t.type != M_AUDTASK", "../sched.c", 496);
|
|
|
|
sc->curRSPTask->state |= OS_SC_YIELD;
|
|
|
|
// Send yield request
|
|
osSpTaskYield();
|
|
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("%08d:osSpTaskYield\n", (u32)(OS_CYCLES_TO_USEC(osGetTime())));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the framebuffer the gfx task wants to use is allowed
|
|
*/
|
|
OSScTask* Sched_GfxTaskFramebufferValid(Scheduler* sc, OSScTask* task) {
|
|
if (task == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// If there are pending swaps, wait until there are none (within 2 VI)
|
|
|
|
if (sc->pendingSwapBuf1 != NULL) {
|
|
if (0) {
|
|
ASSERT(sc->pendingSwapBuf1 != NULL, "sc->pending_swapbuffer1", "../sched.c", UNK_LINE);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
if (sc->pendingSwapBuf2 != NULL) {
|
|
if (0) {
|
|
ASSERT(sc->pendingSwapBuf2 != NULL, "sc->pending_swapbuffer2", "../sched.c", UNK_LINE);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// If the task's framebuffer is one of the pending swaps or NULL.
|
|
// In conjunction with the above, these checks are redundant as the pending swap buffers will only be
|
|
// NULL here, so these could have been simplified to checks for the task's framebuffer being non-NULL.
|
|
|
|
if (((sc->pendingSwapBuf2 != NULL) ? sc->pendingSwapBuf2->swapBuffer : NULL) == task->framebuffer->framebuffer) {
|
|
return NULL;
|
|
}
|
|
|
|
if (((sc->pendingSwapBuf1 != NULL) ? sc->pendingSwapBuf1->swapBuffer : NULL) == task->framebuffer->framebuffer) {
|
|
return NULL;
|
|
}
|
|
|
|
// If the task's framebuffer is the current framebuffer, abort
|
|
|
|
if (osViGetCurrentFramebuffer() == task->framebuffer->framebuffer) {
|
|
return NULL;
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
/**
|
|
* Schedules the next tasks to run on the RSP and RDP
|
|
*
|
|
* @param sc Scheduler
|
|
* @param spTaskOut Next task to run on the RSP
|
|
* @param dpTaskOut Next task to run on the RDP
|
|
* @param state Bits containing whether the RSP and RDP are currently in use
|
|
* @return Bits containing whether the RSP and RDP will be in use after starting the next tasks
|
|
*/
|
|
s32 Sched_Schedule(Scheduler* sc, OSScTask** spTaskOut, OSScTask** dpTaskOut, s32 state) {
|
|
s32 nextState = state;
|
|
OSScTask* gfxTask = sc->gfxListHead;
|
|
OSScTask* audioTask = sc->audioListHead;
|
|
|
|
if (sc->doAudio && (state & OS_SC_SP)) {
|
|
// Audio Task, RSP is available
|
|
|
|
// Return next audio task
|
|
*spTaskOut = audioTask;
|
|
// RSP required
|
|
nextState &= ~OS_SC_SP;
|
|
//! @bug If there is more than one audio task in the queue at any time, unsetting doAudio here
|
|
//! will cause only one task to be processed until a new audio task is enqueued. In practice, audio
|
|
//! tasks are sent infrequently enough that there are never two audio tasks in the queue.
|
|
sc->doAudio = false;
|
|
// Advance task queue
|
|
sc->audioListHead = sc->audioListHead->next;
|
|
if (sc->audioListHead == NULL) {
|
|
sc->audioListTail = NULL;
|
|
}
|
|
} else if (gfxTask != NULL) {
|
|
// GFX Task
|
|
|
|
if ((gfxTask->state & OS_SC_YIELDED) || !(gfxTask->flags & OS_SC_NEEDS_RDP)) {
|
|
// If this is a yielded GFX task, or the RDP is not needed for this GFX task
|
|
|
|
if (state & OS_SC_SP) {
|
|
// If the RSP is available, return next graphics task
|
|
|
|
*spTaskOut = gfxTask;
|
|
// RSP required
|
|
nextState &= ~OS_SC_SP;
|
|
// Advance task queue
|
|
sc->gfxListHead = sc->gfxListHead->next;
|
|
if (sc->gfxListHead == NULL) {
|
|
sc->gfxListTail = NULL;
|
|
}
|
|
}
|
|
} else if (state == (OS_SC_SP | OS_SC_DP)) {
|
|
// Both the RSP and RDP are available, check requested framebuffer
|
|
if (gfxTask->framebuffer == NULL || Sched_GfxTaskFramebufferValid(sc, gfxTask) != NULL) {
|
|
// Return next graphics task
|
|
*spTaskOut = *dpTaskOut = gfxTask;
|
|
// RSP and RDP both required
|
|
nextState &= ~(OS_SC_SP | OS_SC_DP);
|
|
// Advance task queue
|
|
sc->gfxListHead = sc->gfxListHead->next;
|
|
if (sc->gfxListHead == NULL) {
|
|
sc->gfxListTail = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nextState;
|
|
}
|
|
|
|
/**
|
|
* Sets the next framebuffer to the framebuffer associated to `task`.
|
|
* If there is no current buffer or it is time to swap, this buffer will be swapped to
|
|
* immediately, otherwise it will be swapped to later in Sched_HandleRetrace.
|
|
*
|
|
* @see Sched_HandleRetrace
|
|
*/
|
|
void Sched_SetNextFramebufferFromTask(Scheduler* sc, OSScTask* task) {
|
|
if (sc->pendingSwapBuf1 == NULL) {
|
|
sc->pendingSwapBuf1 = task->framebuffer;
|
|
|
|
LogUtils_CheckValidPointer("sc->pending_swapbuffer1", sc->pendingSwapBuf1, "../sched.c", 618);
|
|
|
|
if (sc->curBuf == NULL || sc->curBuf->updateTimer <= 0) {
|
|
Sched_SwapFrameBuffer(sc, task->framebuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the task is done, i.e. it is no longer running on either the RSP or RDP.
|
|
* If so, send a message to the task's message queue if there is one, and swap the framebuffer
|
|
* if required.
|
|
*/
|
|
u32 Sched_TaskComplete(Scheduler* sc, OSScTask* task) {
|
|
// Check that the task has released both the RSP and RDP. For graphics tasks that use both,
|
|
// the RSP will typically finish before the RDP, as the RSP can halt while the RDP is still
|
|
// working through the command buffer.
|
|
if (!(task->state & (OS_SC_DP | OS_SC_SP))) {
|
|
// Send a message to the notify queue if there is one
|
|
if (task->msgQueue != NULL) {
|
|
osSendMesg(task->msgQueue, task->msg, OS_MESG_BLOCK);
|
|
}
|
|
|
|
// Swap the framebuffer if needed
|
|
if (task->flags & OS_SC_SWAPBUFFER) {
|
|
Sched_SetNextFramebufferFromTask(sc, task);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Runs the next tasks. The scheduler doesn't support running RDP tasks without
|
|
* passthrough via the RSP, if there is no RSP task to run then the RDP task will
|
|
* also do nothing.
|
|
*/
|
|
void Sched_RunTask(Scheduler* sc, OSScTask* spTask, OSScTask* dpTask) {
|
|
ASSERT(sc->curRSPTask == NULL, "sc->curRSPTask == NULL", "../sched.c", 663);
|
|
|
|
// If there is no RSP task there's nothing to do.
|
|
if (spTask != NULL) {
|
|
if (spTask->list.t.type == M_NULTASK) {
|
|
// NULTASK is a sync/flush operation, clear current RSP and RDP tasks
|
|
// and unset flags for this task
|
|
if (spTask->flags & OS_SC_NEEDS_RSP) {
|
|
spTask->state &= ~OS_SC_SP;
|
|
sc->curRSPTask = NULL;
|
|
}
|
|
if (spTask->flags & OS_SC_NEEDS_RDP) {
|
|
spTask->state &= ~OS_SC_DP;
|
|
sc->curRDPTask = NULL;
|
|
}
|
|
// Finalize
|
|
Sched_TaskComplete(sc, spTask);
|
|
return;
|
|
}
|
|
|
|
spTask->state &= ~(OS_SC_YIELD | OS_SC_YIELDED);
|
|
// Write back data cache and load the OSTask into the RSP
|
|
osWritebackDCacheAll();
|
|
osSpTaskLoad(&spTask->list);
|
|
|
|
// Begin profiling timers
|
|
if (spTask->list.t.type == M_AUDTASK) {
|
|
sRSPAudioStartTime = osGetTime();
|
|
} else if (spTask->list.t.type == M_GFXTASK) {
|
|
sRSPGFXStartTime = osGetTime();
|
|
} else {
|
|
sRSPOtherStartTime = osGetTime();
|
|
}
|
|
|
|
// Run RSP
|
|
osSpTaskStartGo(&spTask->list);
|
|
|
|
if (sLogScheduler) {
|
|
osSyncPrintf(
|
|
"%08d:osSpTaskStartGo(%08x) %s\n", (u32)OS_CYCLES_TO_USEC(osGetTime()), &spTask->list,
|
|
(spTask->list.t.type == M_AUDTASK ? "AUDIO" : (spTask->list.t.type == M_GFXTASK ? "GRAPH" : "OTHER")));
|
|
}
|
|
|
|
// Set currently running RSP task
|
|
sc->curRSPTask = spTask;
|
|
|
|
// If the task also uses the RDP, set current running RDP task
|
|
if (spTask == dpTask && sc->curRDPTask == NULL) {
|
|
sc->curRDPTask = dpTask;
|
|
sRDPStartTime = sRSPGFXStartTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs when the scheduler has received a notification, either from another thread or
|
|
* on VI Retrace. Tasks that have been sent to it will be enqueued onto the audio or
|
|
* gfx task queue and one may be ran if the RSP is available.
|
|
*/
|
|
void Sched_HandleNotification(Scheduler* sc) {
|
|
OSScTask* nextRSP = NULL;
|
|
OSScTask* nextRDP = NULL;
|
|
s32 state;
|
|
OSScTask* task = NULL;
|
|
|
|
// Enqueue any tasks sent by other threads
|
|
while (osRecvMesg(&sc->cmdQueue, (OSMesg*)&task, OS_MESG_NOBLOCK) != -1) {
|
|
Sched_QueueTask(sc, task);
|
|
}
|
|
|
|
// If an audio task has been enqueued and there is currently an RSP task running,
|
|
// signal to the currently running task to yield the RSP so that the audio task may
|
|
// be ran as soon as possible.
|
|
if (sc->doAudio && sc->curRSPTask != NULL) {
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("[YIELD B]");
|
|
}
|
|
Sched_Yield(sc);
|
|
return;
|
|
}
|
|
|
|
// Run next task in the queue if there is one and the necessary resources are available
|
|
state = ((sc->curRSPTask == NULL) << 1) | (sc->curRDPTask == NULL);
|
|
if (Sched_Schedule(sc, &nextRSP, &nextRDP, state) != state) {
|
|
Sched_RunTask(sc, nextRSP, nextRDP);
|
|
}
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("EN sc:%08x sp:%08x dp:%08x state:%x\n", sc, nextRSP, nextRDP, state);
|
|
}
|
|
}
|
|
|
|
void Sched_HandleRetrace(Scheduler* sc) {
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("%08d:scHandleRetrace %08x\n", (u32)OS_CYCLES_TO_USEC(osGetTime()), osViGetCurrentFramebuffer());
|
|
}
|
|
ViConfig_UpdateBlack();
|
|
sc->retraceCount++;
|
|
|
|
// Retrace handlers run after VI context swap. The last swap buffer may now be the current buffer.
|
|
if (osViGetCurrentFramebuffer() == ((sc->pendingSwapBuf1 != NULL) ? sc->pendingSwapBuf1->swapBuffer : NULL)) {
|
|
if (sc->curBuf != NULL) {
|
|
sc->curBuf->unk_10 = 0;
|
|
}
|
|
if (sc->pendingSwapBuf1 != NULL) {
|
|
sc->pendingSwapBuf1->unk_10 = 0;
|
|
}
|
|
// Advance buffers
|
|
sc->curBuf = sc->pendingSwapBuf1;
|
|
sc->pendingSwapBuf1 = sc->pendingSwapBuf2;
|
|
sc->pendingSwapBuf2 = NULL;
|
|
}
|
|
|
|
if (sc->curBuf != NULL) {
|
|
// Swap the framebuffer when the update timer runs out
|
|
if (sc->curBuf->updateTimer > 0) {
|
|
sc->curBuf->updateTimer--;
|
|
}
|
|
if (sc->curBuf->updateTimer <= 0 && sc->pendingSwapBuf1 != NULL) {
|
|
Sched_SwapFrameBuffer(sc, sc->pendingSwapBuf1);
|
|
}
|
|
}
|
|
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("%08x %08x %08x %d\n", osViGetCurrentFramebuffer(), osViGetNextFramebuffer(),
|
|
(sc->pendingSwapBuf1 != NULL) ? sc->pendingSwapBuf1->swapBuffer : NULL,
|
|
(sc->curBuf != NULL) ? sc->curBuf->updateTimer : 0);
|
|
}
|
|
|
|
// Run the notification handler to enqueue any waiting tasks and possibly run one
|
|
Sched_HandleNotification(sc);
|
|
}
|
|
|
|
/**
|
|
* RSP has signalled that the task has either completed or yielded.
|
|
*/
|
|
void Sched_HandleRSPDone(Scheduler* sc) {
|
|
OSScTask* curRSPTask;
|
|
OSScTask* nextRSP = NULL;
|
|
OSScTask* nextRDP = NULL;
|
|
s32 state;
|
|
|
|
ASSERT(sc->curRSPTask != NULL, "sc->curRSPTask", "../sched.c", 819);
|
|
|
|
// Task profiling
|
|
if (sc->curRSPTask->list.t.type == M_AUDTASK) {
|
|
gRSPAudioTotalTime += osGetTime() - sRSPAudioStartTime;
|
|
} else if (sc->curRSPTask->list.t.type == M_GFXTASK) {
|
|
gRSPGFXTotalTime += osGetTime() - sRSPGFXStartTime;
|
|
} else {
|
|
gRSPOtherTotalTime += osGetTime() - sRSPOtherStartTime;
|
|
}
|
|
|
|
// Clear current RSP task
|
|
curRSPTask = sc->curRSPTask;
|
|
sc->curRSPTask = NULL;
|
|
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("RSP DONE %d %d", curRSPTask->state & OS_SC_YIELD, osSpTaskYielded(&curRSPTask->list));
|
|
}
|
|
|
|
if ((curRSPTask->state & OS_SC_YIELD) && osSpTaskYielded(&curRSPTask->list)) {
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("[YIELDED]\n");
|
|
}
|
|
// Task yielded, set yielded state
|
|
curRSPTask->state |= OS_SC_YIELDED;
|
|
// Add it to the front of the queue
|
|
curRSPTask->next = sc->gfxListHead;
|
|
sc->gfxListHead = curRSPTask;
|
|
if (sc->gfxListTail == NULL) {
|
|
sc->gfxListTail = curRSPTask;
|
|
}
|
|
} else {
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("[NOT YIELDED]\n");
|
|
}
|
|
// Task has completed on the RSP, unset RSP flag and check if the task is fully complete
|
|
curRSPTask->state &= ~OS_SC_SP;
|
|
Sched_TaskComplete(sc, curRSPTask);
|
|
}
|
|
|
|
// Run next task in the queue if there is one and the necessary resources are available
|
|
state = ((sc->curRSPTask == NULL) << 1) | (sc->curRDPTask == NULL);
|
|
if (Sched_Schedule(sc, &nextRSP, &nextRDP, state) != state) {
|
|
Sched_RunTask(sc, nextRSP, nextRDP);
|
|
}
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("SP sc:%08x sp:%08x dp:%08x state:%x\n", sc, nextRSP, nextRDP, state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* RDP has signalled task done upon reaching a DPFullSync command
|
|
*/
|
|
void Sched_HandleRDPDone(Scheduler* sc) {
|
|
OSScTask* curTask;
|
|
OSScTask* nextRSP = NULL;
|
|
OSScTask* nextRDP = NULL;
|
|
s32 state;
|
|
|
|
// Task profiling
|
|
gRDPTotalTime = osGetTime() - sRDPStartTime;
|
|
|
|
// Sanity check
|
|
ASSERT(sc->curRDPTask != NULL, "sc->curRDPTask", "../sched.c", 878);
|
|
ASSERT(sc->curRDPTask->list.t.type == M_GFXTASK, "sc->curRDPTask->list.t.type == M_GFXTASK", "../sched.c", 879);
|
|
|
|
// Clear current RDP task
|
|
curTask = sc->curRDPTask;
|
|
sc->curRDPTask = NULL;
|
|
|
|
// Task has completed on the RDP, unset RDP flag and check if the task is fully complete
|
|
curTask->state &= ~OS_SC_DP;
|
|
Sched_TaskComplete(sc, curTask);
|
|
|
|
// Run next task in the queue if there is one and the necessary resources are available
|
|
state = ((sc->curRSPTask == NULL) << 1) | (sc->curRDPTask == NULL);
|
|
if (Sched_Schedule(sc, &nextRSP, &nextRDP, state) != state) {
|
|
Sched_RunTask(sc, nextRSP, nextRDP);
|
|
}
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("DP sc:%08x sp:%08x dp:%08x state:%x\n", sc, nextRSP, nextRDP, state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by other threads in order to wake the scheduler up immediately to enqueue and
|
|
* possibly run a task that has been sent to the task queue. Otherwise, any pending tasks
|
|
* will be enqueued on next vertical retrace.
|
|
*
|
|
* Original name: osScKickEntryMsg
|
|
*/
|
|
void Sched_Notify(Scheduler* sc) {
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("osScKickEntryMsg\n");
|
|
}
|
|
|
|
osSendMesg(&sc->interruptQueue, (OSMesg)NOTIFY_MSG, OS_MESG_BLOCK);
|
|
}
|
|
|
|
void Sched_ThreadEntry(void* arg) {
|
|
OSMesg msg = NULL;
|
|
Scheduler* sc = (Scheduler*)arg;
|
|
|
|
while (true) {
|
|
if (sLogScheduler) {
|
|
// "%08d: standby"
|
|
osSyncPrintf("%08d:待機中\n", (u32)OS_CYCLES_TO_USEC(osGetTime()));
|
|
}
|
|
|
|
// Await interrupt messages, either from the OS, IrqMgr, or another thread
|
|
osRecvMesg(&sc->interruptQueue, &msg, OS_MESG_BLOCK);
|
|
|
|
switch ((s32)msg) {
|
|
case NOTIFY_MSG:
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("%08d:ENTRY_MSG\n", (u32)OS_CYCLES_TO_USEC(osGetTime()));
|
|
}
|
|
Sched_HandleNotification(sc);
|
|
continue;
|
|
case RSP_DONE_MSG:
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("%08d:RSP_DONE_MSG\n", (u32)OS_CYCLES_TO_USEC(osGetTime()));
|
|
}
|
|
Sched_HandleRSPDone(sc);
|
|
continue;
|
|
case RDP_DONE_MSG:
|
|
if (sLogScheduler) {
|
|
osSyncPrintf("%08d:RDP_DONE_MSG\n", (u32)OS_CYCLES_TO_USEC(osGetTime()));
|
|
}
|
|
Sched_HandleRDPDone(sc);
|
|
continue;
|
|
}
|
|
switch (((OSScMsg*)msg)->type) {
|
|
case OS_SC_RETRACE_MSG:
|
|
Sched_HandleRetrace(sc);
|
|
continue;
|
|
case OS_SC_PRE_NMI_MSG:
|
|
Sched_HandlePreNMI(sc);
|
|
continue;
|
|
case OS_SC_NMI_MSG:
|
|
Sched_HandleNMI(sc);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sched_Init(Scheduler* sc, void* stack, OSPri priority, UNK_TYPE arg3, UNK_TYPE arg4, IrqMgr* irqMgr) {
|
|
bzero(sc, sizeof(Scheduler));
|
|
sc->isFirstSwap = true;
|
|
|
|
// Create message queues for receiving interrupt events and tasks
|
|
osCreateMesgQueue(&sc->interruptQueue, sc->interruptMsgBuf, ARRAY_COUNT(sc->interruptMsgBuf));
|
|
osCreateMesgQueue(&sc->cmdQueue, sc->cmdMsgBuf, ARRAY_COUNT(sc->cmdMsgBuf));
|
|
osSetEventMesg(OS_EVENT_SP, &sc->interruptQueue, (OSMesg)RSP_DONE_MSG);
|
|
osSetEventMesg(OS_EVENT_DP, &sc->interruptQueue, (OSMesg)RDP_DONE_MSG);
|
|
IrqMgr_AddClient(irqMgr, &sc->irqClient, &sc->interruptQueue);
|
|
osCreateThread(&sc->thread, THREAD_ID_SCHED, Sched_ThreadEntry, sc, stack, priority);
|
|
osStartThread(&sc->thread);
|
|
}
|