Files
mm/src/boot/irqmgr.c
T
Derek Hensley f26e77ba40 More General Cleanup (#1638)
* LINKER_FILES in makefile

* COLPOLY_GET_NORMAL

* math header

* libc

* M_PI for cosf and sinf files

* MAXFLOAT

* Revert "MAXFLOAT"

This reverts commit 96b75ffaa8.

* Remove SHT_MINV

* SHRT_MAX

* Add M_PI

* Angle macros

* f suffix

* Format
2024-06-03 14:33:03 -04:00

243 lines
8.0 KiB
C

/**
* @file irqMgr.c
*
* This file implements a manager for forwarding three key system interrupt events to
* registered clients.
* Together with sched.c, these systems implement the libultra video and task scheduling
* model from the libultra "sched" module, with improved functionality in the handling of
* Pre-NMI related events.
*
* The interrupts the IRQ manager deals with are:
* - Vertical Retrace
* This event is sent to the IRQ manager by the OS VI manager which only supports
* the forwarding of VI events to a single message queue. The IRQ manager will
* forward these events to every registered client. Vertical retrace events are
* received when the Video Interface has reached the start of the vertical blanking
* interval, happening at approximately 60Hz on NTSC and 50Hz on PAL. Many threads
* sit idle until a vertical retrace event wakes them up, at which point they will
* perform their task and then return to idle to await the next retrace.
*
* - Pre-NMI
* This event is sent to the IRQ manager by the OS Interrupt Handler when the reset
* button on the N64 control deck is pressed. This event is forwarded to clients to
* inform them that a reset will occur in at least 0.5s / 500ms so they may begin any
* shutdown procedures.
*
* - NMI
* This event is sent at 450ms into the Pre-NMI phase, informing clients that the
* mandatory 0.5s of Pre-NMI is almost over and a reset may be imminent. This event
* is not to be confused with the hardware NMI interrupt signalled when the CPU is
* to fully reset, as by the time that interrupt is received there is no time left
* to do anything.
*
* @see sched.c
*/
#include "irqmgr.h"
#include "stdbool.h"
#include "macros.h"
#include "scheduler.h"
#include "stackcheck.h"
#include "z64thread.h"
vs32 gIrqMgrResetStatus = IRQ_RESET_STATUS_IDLE;
volatile OSTime sIrqMgrResetTime = 0;
volatile OSTime gIrqMgrRetraceTime = 0;
s32 sIrqMgrRetraceCount = 0;
// Internal messages
#define IRQ_RETRACE_MSG 666
#define IRQ_PRENMI_MSG 669
#define IRQ_PRENMI450_MSG 671
#define IRQ_PRENMI480_MSG 672
#define IRQ_PRENMI500_MSG 673
/**
* Registers a client and an associated message queue with the IRQ manager. When an
* interrupt event is received by the IRQ manager, these clients will be notified of
* the event.
*
* @param irqMgr the IrqMgr instance to register with.
* @param client client to register.
* @param msgQueue message queue to send notifications of interrupts to, associated with the client.
*/
void IrqMgr_AddClient(IrqMgr* irqMgr, IrqMgrClient* client, OSMesgQueue* msgQueue) {
u32 saveMask = osSetIntMask(OS_IM_NONE);
client->queue = msgQueue;
client->next = irqMgr->clients;
irqMgr->clients = client;
osSetIntMask(saveMask);
if (irqMgr->resetStatus >= IRQ_RESET_STATUS_PRENMI) {
osSendMesg(client->queue, (OSMesg)&irqMgr->prenmiMsg, OS_MESG_NOBLOCK);
}
if (irqMgr->resetStatus >= IRQ_RESET_STATUS_NMI) {
osSendMesg(client->queue, (OSMesg)&irqMgr->nmiMsg, OS_MESG_NOBLOCK);
}
}
void IrqMgr_RemoveClient(IrqMgr* irqMgr, IrqMgrClient* client) {
IrqMgrClient* iter = irqMgr->clients;
IrqMgrClient* last = NULL;
u32 saveMask = osSetIntMask(OS_IM_NONE);
while (iter != NULL) {
if (iter == client) {
if (last != NULL) {
last->next = client->next;
} else {
irqMgr->clients = client->next;
}
break;
}
last = iter;
iter = iter->next;
}
osSetIntMask(saveMask);
}
/**
* Send `msg` to every registered client if the message queue is not full. The message is
* appended to the back of the queue.
*/
void IrqMgr_SendMesgToClients(IrqMgr* irqMgr, OSMesg msg) {
IrqMgrClient* client = irqMgr->clients;
while (client != NULL) {
osSendMesg(client->queue, msg, OS_MESG_NOBLOCK);
client = client->next;
}
}
/**
* Send `msg` to every registered client if the message queue is not full. This appears to be for
* high-priority messages that should be jammed to the front of the queue, however a bug prevents
* this from working in this way and the message is appended to the back of the queue as in
* `IrqMgr_SendMesgToClients`.
*
* @see IrqMgr_SendMesgToClients
*/
void IrqMgr_JamMesgToClients(IrqMgr* irqMgr, OSMesg msg) {
IrqMgrClient* client = irqMgr->clients;
while (client != NULL) {
if (!MQ_IS_FULL(client->queue)) {
//! @bug The function's name suggests this would use osJamMesg rather than osSendMesg, using the
//! latter makes this function no different than IrqMgr_SendMesgToClients.
osSendMesg(client->queue, msg, OS_MESG_NOBLOCK);
}
client = client->next;
}
}
/**
* Runs when the Pre-NMI OS Event is received. This indicates that the console will reset in at least
* 0.5s / 500ms. Updates the reset status and time before forwarding the Pre-NMI message to registered
* clients so they may begin shutting down in advance of the reset.
*/
void IrqMgr_HandlePreNMI(IrqMgr* irqMgr) {
gIrqMgrResetStatus = IRQ_RESET_STATUS_PRENMI;
irqMgr->resetStatus = IRQ_RESET_STATUS_PRENMI;
sIrqMgrResetTime = irqMgr->resetTime = osGetTime();
// Wait .45 seconds then generate a stage 2 prenmi interrupt
osSetTimer(&irqMgr->timer, OS_USEC_TO_CYCLES(450 * 1000), 0, &irqMgr->queue, (OSMesg)IRQ_PRENMI450_MSG);
IrqMgr_JamMesgToClients(irqMgr, (OSMesg)&irqMgr->prenmiMsg);
}
void IrqMgr_CheckStacks(void) {
StackCheck_Check(NULL);
}
void IrqMgr_HandlePRENMI450(IrqMgr* irqMgr) {
gIrqMgrResetStatus = IRQ_RESET_STATUS_NMI;
irqMgr->resetStatus = IRQ_RESET_STATUS_NMI;
// Wait .03 seconds then generate a stage 3 prenmi interrupt
osSetTimer(&irqMgr->timer, OS_USEC_TO_CYCLES(30 * 1000), 0, &irqMgr->queue, (OSMesg)IRQ_PRENMI480_MSG);
IrqMgr_SendMesgToClients(irqMgr, (OSMesg)&irqMgr->nmiMsg);
}
void IrqMgr_HandlePRENMI480(IrqMgr* irqMgr) {
// Wait .52 seconds. After this we will have waited an entire second
osSetTimer(&irqMgr->timer, OS_USEC_TO_CYCLES(520 * 1000), 0, &irqMgr->queue, (OSMesg)IRQ_PRENMI500_MSG);
osAfterPreNMI();
}
void IrqMgr_HandlePRENMI500(IrqMgr* irqMgr) {
IrqMgr_CheckStacks();
}
void IrqMgr_HandleRetrace(IrqMgr* irqMgr) {
if (gIrqMgrRetraceTime == 0) {
if (irqMgr->retraceTime == 0) {
irqMgr->retraceTime = osGetTime();
} else {
gIrqMgrRetraceTime = osGetTime() - irqMgr->retraceTime;
}
}
sIrqMgrRetraceCount++;
IrqMgr_SendMesgToClients(irqMgr, (OSMesg)&irqMgr->retraceMsg);
}
void IrqMgr_ThreadEntry(void* arg) {
s32 msg = 0;
IrqMgr* irqMgr = (IrqMgr*)arg;
s32 exit = false;
while (!exit) {
osRecvMesg(&irqMgr->queue, (OSMesg*)&msg, OS_MESG_BLOCK);
switch (msg) {
case IRQ_RETRACE_MSG:
IrqMgr_HandleRetrace(irqMgr);
break;
case IRQ_PRENMI_MSG:
IrqMgr_HandlePreNMI(irqMgr);
break;
case IRQ_PRENMI450_MSG:
IrqMgr_HandlePRENMI450(irqMgr);
break;
case IRQ_PRENMI480_MSG:
IrqMgr_HandlePRENMI480(irqMgr);
break;
case IRQ_PRENMI500_MSG:
IrqMgr_HandlePRENMI500(irqMgr);
break;
default:
break;
}
}
}
void IrqMgr_Init(IrqMgr* irqMgr, void* stack, OSPri pri, u8 retraceCount) {
irqMgr->clients = NULL;
irqMgr->retraceMsg.type = OS_SC_RETRACE_MSG;
irqMgr->prenmiMsg.type = OS_SC_PRE_NMI_MSG;
irqMgr->nmiMsg.type = OS_SC_NMI_MSG;
irqMgr->resetStatus = IRQ_RESET_STATUS_IDLE;
irqMgr->resetTime = 0;
osCreateMesgQueue(&irqMgr->queue, irqMgr->msgBuf, ARRAY_COUNT(irqMgr->msgBuf));
osSetEventMesg(OS_EVENT_PRENMI, &irqMgr->queue, (OSMesg)IRQ_PRENMI_MSG);
osViSetEvent(&irqMgr->queue, (OSMesg)IRQ_RETRACE_MSG, retraceCount);
osCreateThread(&irqMgr->thread, Z_THREAD_ID_IRQMGR, IrqMgr_ThreadEntry, irqMgr, stack, pri);
osStartThread(&irqMgr->thread);
}