= Dolphin OS interrupt handling reversing by org. = This document can help you to create your own GAMECUBE interrupt handling code (even in your own OS?). I hope it will be also useful for authors of GC emulators. Send your comments or questions to ogamespec@gmail.com 1. Overview. PPC has so called, "external interrupt enable" bit, in MSR register (MSR[EE] later). External interrupt is generated only when this bit is set. (Actually, this bit is not allowing to raise interrupt signal in CPU). Here is some pseudo-code for interrupt control : --------------------------------------------------------------------------- BOOL OSEnableInterrupts(void) { u32 prev = MSR; MSR |= MSR_EE; return (prev >> 15) & 1; } BOOL OSDisableInterrupts(void) { u32 prev = MSR; MSR &= ~MSR_EE; return (prev >> 15) & 1; } --------------------------------------------------------------------------- External interrupt exception handler is placed at 0x500 memory location in OS "lomem" area. Initialization and OS interrupt handling will be described later. CPU also have access to hardware interrupt mask and cause registers, placed in PI (Processor Interface) : 0C003000 - Interrupt cause (32-bit, R) 0C003004 - Interrupt mask (32-bit, R/W) Referred also as INTSR and INTMR respectively. Note, that given registers location is in physical memory (means raw address for CPU bus transfers). Dolphin OS default memory configuration will map all registers to CC000000 and above, so as example, INTSR will be CC003000. INTSR and INTMR mask bit layout (big-endian) : ----------------------------------------------------------- | bit | name | mask | short description | total | |=====|===========|============|============================| | 15 | RSWST | 0x00010000 | Reset switch state | * | | 18 | HSP | 0x00002000 | High-speed port | 1? | | 19 | DEBUG | 0x00001000 | External debugger | 1? | | 20 | CP | 0x00000800 | Command FIFO | 3 | | 21 | PE FINISH | 0x00000400 | Frame is ready | 1 | | 22 | PE TOKEN | 0x00000200 | Token assertion | 1 | | 23 | VI | 0x00000100 | VI line | 4 | | 24 | MEM | 0x00000080 | Memory protection | 5 | | 25 | DSP | 0x00000040 | DSP | 3 | | 26 | AI | 0x00000020 | Streaming | 1 | | 27 | EXI | 0x00000010 | EXI interrupts | 8 | | 28 | SI | 0x00000008 | Serial transfer | 2 | | 29 | DI | 0x00000004 | Disk | 3 | | 30 | RSW | 0x00000002 | Reset switch | 1 | | 31 | ERROR | 0x00000001 | GP verify error | 1? | ----------------------------------------------------------- * Only for INTSR. This bit is not used in interrupt handling. Actually, each hardware interrupt have several of different interrupts, with one common enable/disable flag. Some documents are referring them as "sources". As example VI have 4 line interrupts (thus 4 sources), you can disable any of them, using VI registers, but PI mask disables them all at once. More to say, that interrupt controlling bits in source device registers are "linked" in some way with INTSR. Im not sure about all details, but it seems that INTSR is working in tricky way. When "interrupt set" bits are cleared in source device, corresponding device bit is also cleared in INTSR. As example : AIDCR has ARINT bit. when ARAM transfer complete interrupt will generate, this bit will set. At same time, INTSR DSP bit will set. Then external interrupt signal will be generated to CPU, and it jumps to interrupt handler (if MSR[EE] allowing it). OS interrupt dispatch handler transfer control to ARAM handler in ARAM library. And finally ARAM handler will clear ARINT bit in AIDCR. At same time (when AIDCR cleared) hardware will clear DSP bit in INTSR. Important note : interrupt mask register also used by hardware. It is not allowing to raise interrupt signal to CPU, but it doesnt affect setting of INTSR bit : INTSR |= interrupt; if(interrupt & INTMR) { Exception(EXTERNAL_INTERRUPT) } So OS interrupt handler can take care about delayed interrupts in future. OS has handy API, to enable specified interrupt and install interrupt handlers. This API is using its own interrupt mask, just for convenience : ------------------------------------------------------------------------- | bit | name | mask | short description | |=====|=============|============|========================================| | 0 | MEM 0 | 0x80000000 | Memory page protection interrupts | | 1 | MEM 1 | 0x40000000 | | | 2 | MEM 2 | 0x20000000 | | | 3 | MEM 3 | 0x10000000 | | | 4 | MEM ADDRESS | 0x08000000 | General memory protection | | 5 | DSP AI | 0x04000000 | AI DMA interrupt | | 6 | DSP ARAM | 0x02000000 | ARAM transfer completed | | 7 | DSP DSP | 0x01000000 | DSP interrupt (reset, halt, mailbox) | | 8 | AI AI | 0x00800000 | Streamed sample counter interrupt | | 9 | EXI 0 EXI | 0x00400000 | EXI hardware interrupts : | | 10 | EXI 0 TC | 0x00200000 | | | 11 | EXI 0 EXT | 0x00100000 | EXI - external device attached | | 12 | EXI 1 EXI | 0x00080000 | EXT - external device detached | | 13 | EXI 1 TC | 0x00040000 | TC - EXI bus transfer completed | | 14 | EXI 1 EXT | 0x00020000 | | | 15 | EXI 2 EXI | 0x00010000 | | | 16 | EXI 2 TC | 0x00008000 | | | 17 | CP | 0x00004000 | See above | | 18 | PE TOKEN | 0x00002000 | | | 19 | PE FINISH | 0x00001000 | | | 20 | SI | 0x00000800 | Serial interrupts | | 21 | DI | 0x00000400 | Disk interrupt | | 22 | RSW | 0x00000200 | Reset switch pressed / released | | 23 | ERROR | 0x00000100 | Various GX hardware errors | | 24 | VI | 0x00000080 | VI retrace (assumed as line 0) | | 25 | DEBUG | 0x00000040 | Debug hardware (GDEV, DDH) | | 26 | HSP | 0x00000020 | High-speed port | ------------------------------------------------------------------------- 2. Interrupt OS initialization. Initialization of exceptions and interrupts is located in OSInit() : --------------------------------------------------------------------------- void OSInit(void) { ... // install default exception handlers OSExceptionInit(); . . . // install default interrupt handler __OSInterruptInit(); ... } --------------------------------------------------------------------------- First we must install default exception "stubs" to prevent crashes from unhandled exception. Here is default exception handler : --------------------------------------------------------------------------- void OSDefaultExceptionHandler(__OSException exception, OSContext *context, u32 dsisr, u32 dar) { // Save context (see OSException.h) OS_EXCEPTION_SAVE_GPRS(context); // Take control to unhandled exception handler (halt the system) __OSUnhandledException(exception, context, dsisr, dar); } (Exception initialization code is not shown here) --------------------------------------------------------------------------- After that, __OSInterruptInit() installs interrupt exception handler : --------------------------------------------------------------------------- u32 __OSPrevInterruptMask AT_ADDRESS(OS_BASE_CACHED | 0x00C4); u32 __OSInterruptMask AT_ADDRESS(OS_BASE_CACHED | 0x00C8); void (*InterruptHandlerTable[32])( // installed handlers __OSInterrupt interrupt, OSContext* context ); void *(*__OSInterruptHandlerTable[32])( // duplicate pointer for debug __OSInterrupt interrupt, OSContext* context ) AT_ADDRESS(OS_BASE_CACHED | 0x3040); void __OSInterruptInit() { // Save interrupt table pointer in lomem (for debugger) *__OSInterruptHandlerTable = InterruptHandlerTable; // Clear interrupt table memset(InterruptHandlerTable, 0, sizeof(InterruptHandlerTable)); // Masks __OSPrevInterruptMask = 0; // clear prev OS interrupt mask *(u32 *)0xCC003004 = 0x000000F0; // set PI interrupt mask __OSMaskInterrupts(0xFFFFFFE0); // mask all interrupts // Set interrupt exception handler at 0x500 __OSSetExceptionHandler(__OS_EXCEPTION_EXTERNAL_INTERRUPT, ExternalInterruptHandler); } void ExternalInterruptHandler(__OSException exception, OSContext *context, u32 dsisr, u32 dar) { #pragma unused (dsisr) #pragma unused (dar) // Save context (see OSException.h) OS_EXCEPTION_SAVE_GPRS(context); // Transfer control to interrupt dispatch handler __OSDispatchInterrupt(exception, context); } --------------------------------------------------------------------------- Interrupt dispatch handler is major driving force in thread switching. Dolphin OS is rescheduling active threads after every interrupt, so it doesnt have fixed "time slice" interval. But it guarantees, that threads are rescheduling at least every 1/50 seconds (1/60, 1/25, 1/30 - depending on video mode and interlace). It is very important detail for high level GC emulators. BTW, such thread switching behaviour is bringing unstability in Dolphin OS. Thats why some games are showing "Green Screen of Death" sometimes. Nintendo probably should add following patch in __OSReschedule routine : static OSTime TimeSlice; void __OSReschedule(void) { OSTime now = OSGetTime(); if((now - TimeSlice) >= OSMillisecondsToTicks(20)) { TimeSlice = now; } else return; // not yet.. // reschedule .. .. } --------------------------------------------------------------------------- // hardware interrupt mask #define PI_INTERRUPT_HSP 0x2000 #define PI_INTERRUPT_DEBUG 0x1000 #define PI_INTERRUPT_CP 0x0800 #define PI_INTERRUPT_PE_FINISH 0x0400 #define PI_INTERRUPT_PE_TOKEN 0x0200 #define PI_INTERRUPT_VI 0x0100 #define PI_INTERRUPT_MEM 0x0080 #define PI_INTERRUPT_DSP 0x0040 #define PI_INTERRUPT_AI 0x0020 #define PI_INTERRUPT_EXI 0x0010 #define PI_INTERRUPT_SI 0x0008 #define PI_INTERRUPT_DI 0x0004 #define PI_INTERRUPT_RSW 0x0002 #define PI_INTERRUPT_ERROR 0x0001 // Handle incoming interrupts and reschedule threads void __OSDispatchInterrupt(OSContext *context) { u32 intsr, cause; u32 unmasked, *prio; __OSInterrupt interrupt; __OSInterruptHandler handler; intsr = *(u32 *)0xCC003000 & ~0x10000; // we no need RSW state // return, if no interrupts pending, or interrupts are masked if( (intsr == 0) || ((intsr & *(u32 *)0xCC003004) == 0) ) { OSLoadContext(context); } // its time to collect interrupts cause = 0; if(intsr & PI_INTERRUPT_MEM) { u16 reg = *(u16 *)0xCC00401E; // MI interrupt cause if(reg & 0x0001) cause |= OS_INTERRUPTMASK_MEM_0; if(reg & 0x0002) cause |= OS_INTERRUPTMASK_MEM_1; if(reg & 0x0004) cause |= OS_INTERRUPTMASK_MEM_2; if(reg & 0x0008) cause |= OS_INTERRUPTMASK_MEM_3; if(reg & 0x0010) cause |= OS_INTERRUPTMASK_MEM_ADDRESS; } // Audio DMA counter=0, ARAM transfer complete or DSP interrupt if(intsr & PI_INTERRUPT_DSP) { u16 reg = *(u16 *)0xCC00500A; // AIDCR if(reg & 0x0008) cause |= OS_INTERRUPTMASK_DSP_AI; if(reg & 0x0020) cause |= OS_INTERRUPTMASK_DSP_ARAM; if(reg & 0x0080) cause |= OS_INTERRUPTMASK_DSP_DSP; } // Disk streaming sample counter=trigger if(intsr & PI_INTERRUPT_AI) { u32 reg = *(u32 *)0xCC006C00; // AICR if(reg & 0x0008) cause |= OS_INTERRUPTMASK_AI; } // EXI bus interrupts if(intsr & PI_INTERRUPT_EXI) { u32 reg = *(u32 *)0xCC006800; // EXI0 CSR if(reg & 0x0002) cause |= OS_INTERRUPTMASK_EXI_0_EXI; if(reg & 0x0008) cause |= OS_INTERRUPTMASK_EXI_0_TC; if(reg & 0x0800) cause |= OS_INTERRUPTMASK_EXI_0_EXT; u32 reg = *(u32 *)0xCC006814; // EXI1 CSR if(reg & 0x0002) cause |= OS_INTERRUPTMASK_EXI_1_EXI; if(reg & 0x0008) cause |= OS_INTERRUPTMASK_EXI_1_TC; if(reg & 0x0800) cause |= OS_INTERRUPTMASK_EXI_1_EXT; u32 reg = *(u32 *)0xCC006828; // EXI2 CSR if(reg & 0x0002) cause |= OS_INTERRUPTMASK_EXI_2_EXI; if(reg & 0x0008) cause |= OS_INTERRUPTMASK_EXI_2_TC; } // No information if(intsr & PI_INTERRUPT_HSP) { cause |= OS_INTERRUPTMASK_PI_HSP; } // No information if(intsr & PI_INTERRUPT_DEBUG) { cause |= OS_INTERRUPTMASK_PI_DEBUG; } // Frame is ready ("draw done") if(intsr & PI_INTERRUPT_PE_FINISH) { cause |= OS_INTERRUPTMASK_PI_PE_FINISH; } // Token marker ("draw sync") if(intsr & PI_INTERRUPT_PE_TOKEN) { cause |= OS_INTERRUPTMASK_PI_PE_TOKEN; } // VI specially configured to generate only one interrupt. // It is assigned at line 0 (or at last line, not sure), // and thus its called vertical retrace interrupt. // Nintendo also planning to add horizontal retrace interrupt in future. // (I dont see any advantages of it, though). if(intsr & PI_INTERRUPT_VI) { cause |= OS_INTERRUPTMASK_PI_VI; } // Serial transfer complete or "RDST" interrupt if(intsr & PI_INTERRUPT_SI) { cause |= OS_INTERRUPTMASK_PI_SI; } // Disk transfer complete, cover opened/closed or DVD break operation if(intsr & PI_INTERRUPT_DI) { cause |= OS_INTERRUPTMASK_PI_DI; } // Reset button pressed/released if(intsr & PI_INTERRUPT_RSW) { cause |= OS_INTERRUPTMASK_PI_RSW; } // Command processor fifo underflow, overflow or breakpoint if(intsr & PI_INTERRUPT_CP) { cause |= OS_INTERRUPTMASK_PI_CP; } // No information if(intsr & PI_INTERRUPT_ERROR) { cause |= OS_INTERRUPTMASK_PI_ERROR; } if(cause &= ~(__OSPrevInterruptMask | __OSInterruptMask)) { prio = InterruptPrioTable; for(;;) { if(unmasked = *prio & cause) { interrupt = cntlzw(unmasked); break; } else prio++; } if(handler = InterruptHandlerTable[interrupt]) { if(interrupt >= __OS_INTERRUPT_DSP_AI) { __OSLastInterrupt = interrupt; __OSLastInterruptTime = OSGetTime(); __OSLastInterruptSrr0 = context->srr0; } OSDisableScheduler(); handler(interrupt, context); OSEnableScheduler(); __OSReschedule(); OSLoadContext(context); } } OSLoadContext(context); } // priority table (high->low) u32 InterruptPrioTable[] = { OS_INTERRUPTMASK_PI_ERROR, OS_INTERRUPTMASK_PI_DEBUG, OS_INTERRUPTMASK_MEM, OS_INTERRUPTMASK_PI_RSW, OS_INTERRUPTMASK_PI_VI, OS_INTERRUPTMASK_PI_PE, OS_INTERRUPTMASK_PI_HSP, OS_INTERRUPTMASK_DSP_ARAM | OS_INTERRUPTMASK_DSP_DSP | OS_INTERRUPTMASK_AI | OS_INTERRUPTMASK_EXI | OS_INTERRUPTMASK_PI_SI | OS_INTERRUPTMASK_PI_DI, OS_INTERRUPTMASK_DSP_AI, OS_INTERRUPTMASK_PI_CP, 0xffffffff }; 3. Interrupt masking (to be added). OSInterruptMask OSGetInterruptMask(void) { return __OSInterruptMask; } OSInterruptMask OSSetInterruptMask(OSInterruptMask mask) { } OSInterruptMask __OSMaskInterrupts(OSInterruptMask mask) { } OSInterruptMask __OSUnmaskInterrupts(OSInterruptMask mask) { } 4. Setting interrupt handlers. __OSInterruptHandler __OSSetInterruptHandler(__OSInterrupt interrupt, __OSInterruptHandler handler) { InterruptHandlerTable[interrupt] = handler; } __OSInterruptHandler __OSGetInterruptHandler(__OSInterrupt interrupt) { return InterruptHandlerTable[interrupt]; } --------------------------------------------------------------------------- Referencies : except.txt - For exceptions info context.txt - See for details of OS context switching thread.txt - Reversing of OS thread management This document is maintaining by Dolwin team. Last edited by org, 15 Aug 2004