mirror of
https://github.com/emu-russia/pureikyubu.git
synced 2025-04-02 10:42:15 -04:00
Docs: New TODO moved to EMU, old renamed to old_todo Docs: Old coding style is deprecated (old_style.txt) Docs: Removed mention about CubeDocumented and fixed old emails RE: boot.s, proved first asm line (lis instruction parameter) RE: Added PAL and NTSC Boot and IPL IDA files RE: Some work on NTSC IPL (identified many lib calls, including OS, GX) RE: Added EXI Bootrom descrambler by segher RE: GXInit RE: Internal GX lib structures (GXPrivate.h) RE: More details on lomem (OS versions) RE: OSInit and OS.c RE: OSAlloc (heap allocator) RE: Very first code of Metrowerk runtime (__start.c) Docs: Added copy of http://gcdev.narod.ru Source\Utils: Command processor (Cmd.c) Source\Utils: File wrapper Source\Utils: Gekko disasm cleaned up and ported to plain C Source\Utils: Double-linked lists Source\Utils: Ported old Profiler code Source\Utils: String utils
501 lines
19 KiB
Text
501 lines
19 KiB
Text
|
|
= 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
|