pureikyubu/Docs/RE/osint.txt
ogamespec c21147ccff Cumulative changes all around project, regarding Stage 1
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
2015-09-04 19:45:10 +03:00

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