/** * @file padmgr.c * * This file implements communicating with joybus devices at a high level and serving the results to other threads. * * Any device that can be plugged into one of the four controller ports such as a standard N64 controller is a joybus * device. Some joybus devices are also located inside the cartridge such as EEPROM for save data or the Real-Time * Clock, however neither of these are used in Zelda64 and so this type of communication is unimplemented. Unlike the * padmgr implementation in OoT, there are three controller types supported: * - Standard N64 Controller * - N64 Mouse * - Voice Recognition Unit * * Communicating with these devices is broken down into various layers: * * Other threads : The rest of the program that will use the polled data * | * PadMgr : Manages devices, submits polling commands at vertical retrace * | * Libultra osCont* routines : Interface for building commands and safely using the Serial Interface * | * Serial Interface : Hardware unit for sending joybus commands and receiving data via DMA * | * PIF : Forwards joybus commands and receives response data from the devices * |---¬---¬---¬-------¬ * 1 2 3 4 5 : The joybus devices plugged into the four controller ports or on the cartridge * * Joybus communication is handled on another thread as polling and receiving controller data is a slow process; the * N64 programming manual section 26.2.4.1 quotes 2 milliseconds as the expected delay from calling * `osContStartReadData` to receiving the data. By running this on a separate thread to the game state, work can be * done while waiting for this operation to complete. */ #include "global.h" #include "PR/controller.h" #include "PR/os_motor.h" #include "libc64/sprintf.h" #include "fault.h" #include "z64voice.h" extern FaultMgr gFaultMgr; #define PADMGR_RETRACE_MSG (1 << 0) #define PADMGR_PRE_NMI_MSG (1 << 1) #define PADMGR_NMI_MSG (1 << 2) typedef enum { /* 0 */ VOICE_INIT_FAILED, // voice initialization failed /* 1 */ VOICE_INIT_TRY, // try to initialize voice /* 2 */ VOICE_INIT_SUCCESS // voice initialized } VoiceInitStatus; PadMgr* sPadMgrInstance = &gPadMgr; s32 sPadMgrRetraceCount = 0; s32 sVoiceInitStatus = VOICE_INIT_TRY; /** * Returns the bitmask of valid controllers. * * bit n set = controller (n+1) available. */ u8 PadMgr_GetValidControllersMask(void) { return sPadMgrInstance->validCtrlrsMask; } /** * Sets the callback function intended to be used to modify the rumble pak state on each VI with * calls to `PadMgr_RumbleSetSingle`. The callback may be passed a single user-provided argument. * * @param callback callback to run before rumble state is updated for the current VI * @param arg the argument to pass to the callback * * @see PadMgr_RumbleSetSingle * @see PadMgr_UnsetRumbleRetraceCallback */ void PadMgr_SetRumbleRetraceCallback(void (*callback)(void*), void* arg) { sPadMgrInstance->rumbleRetraceCallback = callback; sPadMgrInstance->rumbleRetraceArg = arg; } /** * Unsets the rumble retrace callback if the provided function and argument are the same as those already registered. * * @param callback the callback to test against * @param arg the argument to test against * * @see PadMgr_SetRumbleRetraceCallback */ void PadMgr_UnsetRumbleRetraceCallback(void (*callback)(void*), void* arg) { if ((sPadMgrInstance->rumbleRetraceCallback == callback) && (sPadMgrInstance->rumbleRetraceArg == arg)) { sPadMgrInstance->rumbleRetraceCallback = NULL; sPadMgrInstance->rumbleRetraceArg = NULL; } } /** * Sets the callback function intended to be used to test for particular inputs on each VI, immediately after new * inputs have been polled. * * This is useful for the rare cases where additional resolution is needed in the timings of player inputs, * while the game is still updating comparatively slower. * * @param callback callback to run as soon as new inputs are obtained * @param arg the argument to pass to the callback * * @see PadMgr_UnsetInputRetraceCallback */ void PadMgr_SetInputRetraceCallback(void (*callback)(void*), void* arg) { sPadMgrInstance->inputRetraceCallback = callback; sPadMgrInstance->inputRetraceArg = arg; } /** * Unsets the input retrace callback if the provided function and argument are the same as those already registered. * * @param callback the callback to test against * @param arg the argument to test against * * @see PadMgr_SetInputRetraceCallback */ void PadMgr_UnsetInputRetraceCallback(void (*callback)(void*), void* arg) { if ((sPadMgrInstance->inputRetraceCallback == callback) && (sPadMgrInstance->inputRetraceArg == arg)) { sPadMgrInstance->inputRetraceCallback = NULL; sPadMgrInstance->inputRetraceArg = NULL; } } /** * Acquires exclusive access to the serial event queue. * * When a DMA to/from PIF RAM completes, an SI interrupt is generated to notify the process that the DMA has completed * and a message is posted to the serial event queue. If multiple processes are trying to use the SI at the same time * it becomes ambiguous as to which DMA has completed, so a locking system is required to arbitrate access to the SI. * * Once the task requiring the serial event queue is complete, it should be released with a call to * `PadMgr_ReleaseSerialEventQueue()`. * * If another process tries to acquire the event queue, the current thread will be blocked until the event queue is * released. Note the possibility for a deadlock, if the thread that already holds the serial event queue attempts to * acquire it again it will block forever. * * @return The message queue to which SI interrupt events are posted. * * @see PadMgr_ReleaseSerialEventQueue */ OSMesgQueue* PadMgr_AcquireSerialEventQueue(void) { OSMesgQueue* serialEventQueue; osRecvMesg(&sPadMgrInstance->serialLockQueue, (OSMesg*)&serialEventQueue, OS_MESG_BLOCK); return serialEventQueue; } /** * Like `PadMgr_AcquireSerialEventQueue` but for Voice access. * * @see PadMgr_AcquireSerialEventQueue * @see PadMgr_VoiceReleaseSerialEventQueue */ OSMesgQueue* PadMgr_VoiceAcquireSerialEventQueue(void) { OSMesgQueue* serialEventQueue; osRecvMesg(&sPadMgrInstance->serialLockQueue, (OSMesg*)&serialEventQueue, OS_MESG_BLOCK); return serialEventQueue; } /** * Relinquishes access to the serial message queue, allowing another process to acquire and use it. * * @param serialEventQueue The serial message queue acquired by `PadMgr_AcquireSerialEventQueue` * * @see PadMgr_AcquireSerialEventQueue */ void PadMgr_ReleaseSerialEventQueue(OSMesgQueue* serialEventQueue) { osSendMesg(&sPadMgrInstance->serialLockQueue, (OSMesg)serialEventQueue, OS_MESG_BLOCK); } /** * Like `PadMgr_ReleaseSerialEventQueue` but for Voice access. * * @see PadMgr_VoiceAcquireSerialEventQueue */ void PadMgr_VoiceReleaseSerialEventQueue(OSMesgQueue* serialEventQueue) { osSendMesg(&sPadMgrInstance->serialLockQueue, (OSMesg)serialEventQueue, OS_MESG_BLOCK); } /** * Locks controller input data while padmgr is reading new inputs or another thread is using the current inputs. * This prevents new inputs overwriting the current inputs while they are in use. * * @see PadMgr_UnlockPadData */ void PadMgr_LockPadData(void) { osRecvMesg(&sPadMgrInstance->lockQueue, NULL, OS_MESG_BLOCK); } /** * Unlocks controller input data, allowing padmgr to read new inputs or another thread to access the most recently * polled inputs. * * @see PadMgr_LockPadData */ void PadMgr_UnlockPadData(void) { osSendMesg(&sPadMgrInstance->lockQueue, NULL, OS_MESG_BLOCK); } /** * Activates the rumble pak for all controllers it is enabled on, stops it for all controllers it is disabled on and * attempts to initialize it for a controller if it is not already initialized. */ void PadMgr_UpdateRumble(void) { s32 i; s32 ret; OSMesgQueue* serialEventQueue = PadMgr_AcquireSerialEventQueue(); s32 triedRumbleComm = false; for (i = 0; i < MAXCONTROLLERS; i++) { if (sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NORMAL) { // Check status for whether a controller pak is connected if (sPadMgrInstance->padStatus[i].status & CONT_CARD_ON) { if (sPadMgrInstance->pakType[i] == CONT_PAK_RUMBLE) { if (sPadMgrInstance->rumbleEnable[i]) { if (sPadMgrInstance->rumbleTimer[i] < 3) { // Rumble pak start if (osMotorStart(&sPadMgrInstance->rumblePfs[i]) != 0) { // error sPadMgrInstance->pakType[i] = CONT_PAK_NONE; } else { sPadMgrInstance->rumbleTimer[i] = 3; } triedRumbleComm = true; } } else { if (sPadMgrInstance->rumbleTimer[i] != 0) { // Rumble Pak stop if (osMotorStop(&sPadMgrInstance->rumblePfs[i]) != 0) { // error sPadMgrInstance->pakType[i] = CONT_PAK_NONE; } else { sPadMgrInstance->rumbleTimer[i]--; } triedRumbleComm = true; } } } } else if (sPadMgrInstance->pakType[i] != CONT_PAK_NONE) { sPadMgrInstance->pakType[i] = CONT_PAK_NONE; } } } if (!triedRumbleComm) { // Try to initialize the rumble pak for controller port `i` if a controller pak is connected and not already // known to be an initialized a rumble pak i = sPadMgrRetraceCount % MAXCONTROLLERS; if ((sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NORMAL) && (sPadMgrInstance->padStatus[i].status & CONT_CARD_ON) && (sPadMgrInstance->pakType[i] != CONT_PAK_RUMBLE)) { ret = osMotorInit(serialEventQueue, &sPadMgrInstance->rumblePfs[i], i); if (ret == 0) { // Got rumble pak sPadMgrInstance->pakType[i] = CONT_PAK_RUMBLE; sPadMgrInstance->rumbleTimer[i] = 2; } else if (ret == PFS_ERR_DEVICE) { // Not a rumble pak if (sPadMgrInstance->pakType[i] != CONT_PAK_OTHER) { sPadMgrInstance->pakType[i] = CONT_PAK_OTHER; } } else if (ret == PFS_ERR_CONTRFAIL) { // Pak communication error } else if (ret == PFS_ERR_NOPACK) { // No controller pak } else { // Unrecognized return code Fault_AddHungupAndCrash("../padmgr.c", 594); } } } PadMgr_ReleaseSerialEventQueue(serialEventQueue); } /** * Immediately stops rumble on all controllers */ void PadMgr_RumbleStop(void) { OSMesgQueue* serialEventQueue = PadMgr_AcquireSerialEventQueue(); s32 i; for (i = 0; i < MAXCONTROLLERS; i++) { if ((sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NORMAL) && (osMotorInit(serialEventQueue, &sPadMgrInstance->rumblePfs[i], i) == 0)) { // If there is a rumble pak attached to this controller, stop it osMotorStop(&sPadMgrInstance->rumblePfs[i]); } } PadMgr_ReleaseSerialEventQueue(serialEventQueue); } /** * Prevents rumble for 12 VI, ~0.2 seconds at 60 VI/sec */ void PadMgr_RumblePause(void) { sPadMgrInstance->rumbleOffTimer = 12; } /** * Enables or disables rumble on controller port `port` for 24 VI, * ~0.4 seconds at 60 VI/sec and ~0.48 seconds at 50 VI/sec */ void PadMgr_RumbleSetSingle(s32 port, s32 enable) { sPadMgrInstance->rumbleEnable[port] = enable; sPadMgrInstance->rumbleOnTimer = 24; } /** * Enables or disables rumble on all controller ports for 24 VI, * ~0.4 seconds at 60 VI/sec and ~0.48 seconds at 50 VI/sec * * @param enable Array of u8 of length MAXCONTROLLERS containing either true or false to enable or disable rumble * for that controller */ void PadMgr_RumbleSet(u8 enable[MAXCONTROLLERS]) { s32 i; for (i = 0; i < MAXCONTROLLERS; i++) { sPadMgrInstance->rumbleEnable[i] = enable[i]; } sPadMgrInstance->rumbleOnTimer = 24; } s32 PadMgr_ControllerHasRumblePak(s32 port) { return sPadMgrInstance->pakType[port] == CONT_PAK_RUMBLE; } void PadMgr_AdjustInput(Input* input) { s32 plus; s32 minus; s32 pressX; s32 pressX2; s32 pressY; s32 pressY2; s32 pad1; s32 pad2; s32 pad3; f32 pad4; f32 newX; f32 newY; f32 magnitude; f32 angle; s8 curX = input->cur.stick_x; s8 curY = input->cur.stick_y; if (CHECK_BTN_ANY(input->press.button, BTN_RESET) || (input->press.stick_x == 0)) { input->press.stick_x = 61; input->press.errno = -61; input->press.stick_y = 63; input->rel.errno = -63; } pressX = input->press.stick_x; pressX2 = (s8)input->press.errno; pressY = input->press.stick_y; pressY2 = (s8)input->rel.errno; if (CHECK_BTN_ANY(input->cur.button, BTN_RESET)) { minus = curX - 7; plus = curX + 7; if (minus > 0) { if (pressX < minus - 3) { pressX = minus - 3; input->press.stick_x = minus - 3; } } else if (plus < 0) { if (pressX2 > plus + 3) { pressX2 = plus + 3; input->press.errno = plus + 3; } } minus = curY - 7; plus = curY + 7; if (minus > 0) { if (pressY < minus - 3) { pressY = minus - 3; input->press.stick_y = minus - 3; } } else if (plus < 0) { if (pressY2 > plus + 3) { pressY2 = plus + 3; input->rel.errno = plus + 3; } } } minus = curX - 7; plus = curX + 7; if (minus > 0) { newX = (pressX < minus) ? 1.0f : (f32)minus / pressX; } else if (plus < 0) { newX = (pressX2 > plus) ? -1.0f : -(f32)plus / pressX2; } else { newX = 0.0f; } minus = curY - 7; plus = curY + 7; if (minus > 0) { newY = (pressY < minus) ? 1.0f : (f32)minus / pressY; } else if (plus < 0) { newY = (pressY2 > plus) ? -1.0f : -(f32)plus / pressY2; } else { newY = 0.0f; } if (1) { s32 pad; magnitude = sqrtf(SQ(newX) + SQ(newY)); if (magnitude > 1.0f) { magnitude = 1.0f; } angle = Math_Atan2F_XY(curY, -curX); newX = -sinf(angle) * magnitude; newY = cosf(angle) * magnitude; } input->rel.stick_x = newX * 60.5f; input->rel.stick_y = newY * 60.5f; } /** * Updates `sPadMgrInstance->inputs` based on the error response of each controller */ void PadMgr_UpdateInputs(void) { s32 diff; Input* input = &sPadMgrInstance->inputs[0]; s32 i; OSContPad* pad = &sPadMgrInstance->pads[0]; for (i = 0; i < sPadMgrInstance->nControllers; i++, input++, pad++) { s32 isStandardController; input->prev = input->cur; isStandardController = sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NORMAL; if (isStandardController) { switch (pad->errno) { case 0: // No error, copy inputs input->cur = *pad; break; case CHNL_ERR_OVERRUN >> 4: // Overrun error, reuse previous inputs input->cur = input->prev; break; case CHNL_ERR_NORESP >> 4: // No response error, take inputs as 0 input->cur.button = 0; input->cur.stick_x = 0; input->cur.stick_y = 0; input->cur.errno = pad->errno; if (sPadMgrInstance->ctrlrType[i] != PADMGR_CONT_NONE) { // If we get no response, consider the controller disconnected sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_NONE; sPadMgrInstance->pakType[i] = CONT_PAK_NONE; sPadMgrInstance->rumbleTimer[i] = 0xFF; } break; default: // Unknown error response Fault_AddHungupAndCrash("../padmgr.c", 1098); break; } } else { input->cur.button = 0; input->cur.stick_x = 0; input->cur.stick_y = 0; input->cur.errno = pad->errno; } // If opposed directions on the D-Pad are pressed at the same time, mask both out if ((input->cur.button & (BTN_DDOWN | BTN_DUP)) == (BTN_DDOWN | BTN_DUP)) { input->cur.button &= ~(BTN_DDOWN | BTN_DUP); } if ((input->cur.button & (BTN_DRIGHT | BTN_DLEFT)) == (BTN_DRIGHT | BTN_DLEFT)) { input->cur.button &= ~(BTN_DRIGHT | BTN_DLEFT); } // Calculate pressed and relative inputs diff = input->prev.button ^ input->cur.button; input->press.button |= (u16)(diff & input->cur.button); input->rel.button |= (u16)(diff & input->prev.button); if (1) {} PadMgr_AdjustInput(input); } } /** * Looks for plugged in voice recognition units and initializes them. * * This runs only on startup, there will be no attempts to initialize * a VRU other than on the first VI retrace. */ void PadMgr_InitVoice(void) { s32 i; OSMesgQueue* serialEventQueue; s32 ret; for (i = 0; i < sPadMgrInstance->nControllers; i++) { if (sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_VOICE_PLUGGED) { serialEventQueue = PadMgr_AcquireSerialEventQueue(); ret = osVoiceInit(serialEventQueue, &gVoiceHandle, i); PadMgr_ReleaseSerialEventQueue(serialEventQueue); if (ret != 0) { // error } else { sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_VOICE; sVoiceInitStatus = VOICE_INIT_SUCCESS; AudioVoice_Noop(); } } } // If sVoiceInitStatus is still VOICE_INIT_TRY after the first attempt to initialize a VRU, don't try again if (sVoiceInitStatus == VOICE_INIT_TRY) { sVoiceInitStatus = VOICE_INIT_FAILED; } } /** * Update the state of connected controllers */ void PadMgr_UpdateConnections(void) { s32 ctrlrMask = 0; s32 i; char msg[50]; for (i = 0; i < MAXCONTROLLERS; i++) { if (sPadMgrInstance->padStatus[i].errno == 0) { switch (sPadMgrInstance->padStatus[i].type & CONT_TYPE_MASK) { case CONT_TYPE_NORMAL: // Standard N64 Controller ctrlrMask |= (1 << i); if (sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NONE) { sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_NORMAL; } break; case CONT_TYPE_MOUSE: // N64 Mouse if (sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NONE) { sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_MOUSE; } break; case CONT_TYPE_VOICE: // Voice Recognition Unit if (sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NONE) { sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_VOICE_PLUGGED; sPadMgrInstance->pakType[i] = CONT_PAK_NONE; } break; default: // Other/Unrecognized if (sPadMgrInstance->ctrlrType[i] == PADMGR_CONT_NONE) { sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_UNK; // "Recognized an unknown type of controller (%04x)" sprintf(msg, "知らない種類のコントローラ(%04x)を認識しました", sPadMgrInstance->padStatus[i].type); } // Missing break required for matching } } else if (sPadMgrInstance->ctrlrType[i] != PADMGR_CONT_NONE) { // Plugged controller errored sPadMgrInstance->ctrlrType[i] = PADMGR_CONT_NONE; sPadMgrInstance->pakType[i] = CONT_PAK_NONE; sPadMgrInstance->rumbleTimer[i] = 0xFF; } } sPadMgrInstance->validCtrlrsMask = ctrlrMask; } void PadMgr_HandleRetrace(void) { OSMesgQueue* serialEventQueue = PadMgr_AcquireSerialEventQueue(); // Begin reading controller data osContStartReadData(serialEventQueue); // Execute rumble callback if (sPadMgrInstance->rumbleRetraceCallback != NULL) { sPadMgrInstance->rumbleRetraceCallback(sPadMgrInstance->rumbleRetraceArg); } // Wait for controller data osRecvMesg(serialEventQueue, NULL, OS_MESG_BLOCK); osContGetReadData(sPadMgrInstance->pads); // Clear all but controller 1 bzero(&sPadMgrInstance->pads[1], sizeof(*sPadMgrInstance->pads) * (MAXCONTROLLERS - 1)); // If in PreNMI, clear all controllers if (sPadMgrInstance->isResetting) { bzero(sPadMgrInstance->pads, sizeof(sPadMgrInstance->pads)); } // Query controller statuses osContStartQuery(serialEventQueue); osRecvMesg(serialEventQueue, NULL, OS_MESG_BLOCK); osContGetQuery(sPadMgrInstance->padStatus); // Lock serial message queue PadMgr_ReleaseSerialEventQueue(serialEventQueue); // Update connections PadMgr_UpdateConnections(); // Lock input data PadMgr_LockPadData(); // Update input data PadMgr_UpdateInputs(); // Execute input callback if (sPadMgrInstance->inputRetraceCallback != NULL) { sPadMgrInstance->inputRetraceCallback(sPadMgrInstance->inputRetraceArg); } // Unlock input data PadMgr_UnlockPadData(); // Try and initialize a Voice Recognition Unit if not already attempted if (sVoiceInitStatus != VOICE_INIT_FAILED) { PadMgr_InitVoice(); } // Rumble Pak if (gFaultMgr.msgId != 0) { // If fault is active, no rumble PadMgr_RumbleStop(); } else if (sPadMgrInstance->rumbleOffTimer > 0) { // If the rumble off timer is active, no rumble --sPadMgrInstance->rumbleOffTimer; PadMgr_RumbleStop(); } else if (sPadMgrInstance->rumbleOnTimer == 0) { // If the rumble on timer is inactive, no rumble PadMgr_RumbleStop(); } else if (!sPadMgrInstance->isResetting) { // If not resetting, update rumble PadMgr_UpdateRumble(); --sPadMgrInstance->rumbleOnTimer; } sPadMgrRetraceCount++; } void PadMgr_HandlePreNMI(void) { sPadMgrInstance->isResetting = true; PadMgr_RumblePause(); } /** * Fetches the most recently polled inputs from padmgr. * * NOTE: This function does not lock the inputs while reading them. * The padmgr thread may interrupt this function and poll new inputs while the thread running this function is * currently copying the old inputs potentially resulting in a mix of newly polled and old inputs being used. * It is preferable to use `PadMgr_GetInput` or `PadMgr_GetInput2`. * * @param inputs Array of Input of length MAXCONTROLLERS to copy inputs into * @param gameRequest True if polling inputs for updating the game state */ void PadMgr_GetInputNoLock(Input* inputs, s32 gameRequest) { s32 i; Input* input = &sPadMgrInstance->inputs[0]; Input* inputOut = &inputs[0]; s32 buttonDiff; for (i = 0; i < MAXCONTROLLERS; i++) { if (gameRequest) { // Copy inputs as-is, press and rel are calculated prior in `PadMgr_UpdateInputs` *inputOut = *input; // Zero parts of the press and rel inputs in the polled inputs so they are not read more than once input->press.button = 0; input->rel.button = 0; } else { // Take as the previous inputs the inputs that are currently in the destination array inputOut->prev = inputOut->cur; // Copy current inputs from the polled inputs inputOut->cur = input->cur; // Calculate press and rel from these buttonDiff = inputOut->prev.button ^ inputOut->cur.button; inputOut->press.button = inputOut->cur.button & buttonDiff; inputOut->rel.button = inputOut->prev.button & buttonDiff; PadUtils_UpdateRelXY(inputOut); } input++; inputOut++; } } /** * Fetches the most recently polled inputs from padmgr. * * @param inputs Array of Input of length MAXCONTROLLERS to copy inputs into * @param gameRequest True if polling inputs for updating the game state */ void PadMgr_GetInput(Input* inputs, s32 gameRequest) { PadMgr_LockPadData(); PadMgr_GetInputNoLock(inputs, gameRequest); PadMgr_UnlockPadData(); } /** * Copy of `PadMgr_GetInput` * * @see PadMgr_GetInput */ void PadMgr_GetInput2(Input* inputs, s32 gameRequest) { PadMgr_LockPadData(); PadMgr_GetInputNoLock(inputs, gameRequest); PadMgr_UnlockPadData(); } void PadMgr_ThreadEntry() { s16* interruptMsg = NULL; s32 actionBits; s32 exit; osCreateMesgQueue(&sPadMgrInstance->interruptQueue, sPadMgrInstance->interruptMsgBuf, ARRAY_COUNT(sPadMgrInstance->interruptMsgBuf)); IrqMgr_AddClient(sPadMgrInstance->irqMgr, &sPadMgrInstance->irqClient, &sPadMgrInstance->interruptQueue); actionBits = 0; exit = false; while (!exit) { // Process all messages currently in the queue, instead of only a single mssage. // Deduplicates the same message. do { osRecvMesg(&sPadMgrInstance->interruptQueue, (OSMesg*)&interruptMsg, OS_MESG_BLOCK); switch (*interruptMsg) { case OS_SC_RETRACE_MSG: actionBits |= PADMGR_RETRACE_MSG; break; case OS_SC_PRE_NMI_MSG: actionBits |= PADMGR_PRE_NMI_MSG; break; case OS_SC_NMI_MSG: actionBits |= PADMGR_NMI_MSG; break; } } while (!MQ_IS_EMPTY(&sPadMgrInstance->interruptQueue)); // Act on received messages while (actionBits != 0) { if (actionBits & PADMGR_NMI_MSG) { actionBits &= ~PADMGR_NMI_MSG; exit = true; } else if (actionBits & PADMGR_PRE_NMI_MSG) { actionBits &= ~PADMGR_PRE_NMI_MSG; PadMgr_HandlePreNMI(); } else if (actionBits & PADMGR_RETRACE_MSG) { actionBits &= ~PADMGR_RETRACE_MSG; PadMgr_HandleRetrace(); } } } IrqMgr_RemoveClient(sPadMgrInstance->irqMgr, &sPadMgrInstance->irqClient); } void PadMgr_Init(OSMesgQueue* siEvtQ, IrqMgr* irqMgr, OSId threadId, OSPri pri, void* stack) { bzero(sPadMgrInstance, sizeof(PadMgr)); sPadMgrInstance->irqMgr = irqMgr; // These are unique access tokens, there should only ever be room for one OSMesg in these queues osCreateMesgQueue(&sPadMgrInstance->serialLockQueue, &sPadMgrInstance->serialMsg, 1); osCreateMesgQueue(&sPadMgrInstance->lockQueue, &sPadMgrInstance->lockMsg, 1); PadMgr_UnlockPadData(); PadSetup_Init(siEvtQ, &sPadMgrInstance->validCtrlrsMask, sPadMgrInstance->padStatus); sPadMgrInstance->nControllers = MAXCONTROLLERS; osContSetCh(sPadMgrInstance->nControllers); PadMgr_ReleaseSerialEventQueue(siEvtQ); osCreateThread(&sPadMgrInstance->thread, threadId, PadMgr_ThreadEntry, sPadMgrInstance, stack, pri); osStartThread(&sPadMgrInstance->thread); }