
-------------------------------------------------------------------------------
              -= NINTENDO GAMECUBE Initial Program Loader RE =-
-------------------------------------------------------------------------------

   IPL, or bootrom is located inside on-board Macronix chip (near Flipper),
   and connected to EXI bus.

   IPL have mapped CPU address space at 0xFFFnnnnn, so exception prefix
   bit 25 (IP) in MSR is set after CPU reset and system reset vector offset
   is 0xFFF00100.

   Small program, called 'BS' is loading actual program code (menu, RTC stuff)
   at 0x81300000. This part is called 'BS2'. Probably 'BS' stands for 'Boot 
   System', like OS is 'Operating System'.

   IPL cant be readed after boot neither using CPU, not special EXI bus reads.
   After loading of BS2, BS disabling IPL access, by clearing 17th bit of
   0xCC006800, then it sets MSR[IP] bit to normal mode (pointing lower memory),
   and jumps to BS2 code entrypoint.

   BS2 was written on C, using official SDK libraries, probably earlier than
   1.0. __start.c seems to be same as usual, except that there is no OSInit()
   call (old versions should call OSInit() in main, instead of __start).

   Here is the short description of start() routine (you can easily find full
   version in your SDK ;) :

// 81300000
__start:
    __init_registers()          // set stack pointer and static bases (r2, r13)
    __init_hardware()           // paired-singles and cache init
    __init_data()               // clear bss ?

    .                           // here goes Debug Monitor stuff
    .                           
    .

    DBInit()                    // debug monitor init :)
    __init_user()               // cpp init
    main()                      // that's actually, IPL (BS2) code
    jmp exit()                  // halt CPU


main() reversing :
------------------

// 813006D4 (in BS2.c)
main()
{
    BS2Init();
    OSInit();

    AD16Init();
    AD16WriteReg(0x800);

    DVDInit();
    AD16WriteReg(0x900);

    CARDInit();
    AD16WriteReg(0xa00);

    0x81302104();           // SRAM, real-time clock (check ?)

    __VIInit(0);
    VIInit();
    AD16WriteReg(0xb00);

    0x813004e4();           // setup perf. monitor
    __SyncTime();           // update CPU timer by RTC value
    0x813022c0();           // perform initial DVD actions and fall back into menu

    PADSetSpec(5);          // set pad type ('spec') to production
    PADInit();

    AD16WriteReg(0xc00);

    BS2Menu();              // here goes intro and main menu... (BIG one!)

    OSHalt("BS2 ERROR >>> SHOULD NEVER REACH HERE");
}

// ----------------------------------------------------------------------------

static float ZeroF;

// 8130045C
void BS2Init()
{
    // clear lower memory
    memset(0x80000000, 0, 256);
    memset(0x80003000, 0, 256);

    __OSInitBATs();

    // set memory size to 24MB
    *(u32 *)0x80000028 = 0x01800000;

    // set console type to default retail 1
    *(u32 *)0x8000002c = 1;

    // upgrade retail
    *(u32 *)0x8000002c += *(u32 *)0xcc00302c >> 28;

    *(u32 *)&ZeroF = -1;
    __OSInitFPRs();
}

// 813003A0 (removed after SDK 1.0 release)
void __OSInitBATs()
{
    __asm
    {
        isync
        li      r4, 0
        mtspr   DBAT2L, r4
        mtspr   DBAT2U, r4
        mtspr   DBAT3L, r4
        mtspr   DBAT3U, r4
        mtspr   IBAT1L, r4
        mtspr   IBAT1U, r4
        mtspr   IBAT2L, r4
        mtspr   IBAT2U, r4
        mtspr   IBAT3L, r4
        mtspr   IBAT3U, r4
        isync
    }
}

// 813003D8 (from OS.c)
void __OSInitFPRs()
{
    // FP is already initialzied in __start(),
    // so just invalidate all FPRs.
    __asm
    {
        lfs     f0, ZeroF
        fmr     f1, f0
        fmr     f2, f0
        fmr     f3, f0
        . e
        . t
        . c
        fmr     f31, f0
    }
}

// maybe later
0x81302104()
{
    __OSLockSram();
    __OSCheckSram();
    __OSGetRTC();
    OSTickToCalendarTime();
    memset();
    __OSUnlockSram();
    __OSSyncSram();
}

// maybe later
0x813004e4()
{
    OSDisableInterrupts();
    OSGetTick();
    OSGetTick();
    OSGetTick();
    __div2i();
    __div2i();
    PPCMtpmc1();
    PPCMtmmcr0();
    OSGetTick();
    OSGetTick();
    PPCMtmmcr0();
    PPCMfpmc1();
    __div2i();
    __div2i();
    __div2i();
    OSRestoreInterrupts();
}

// update time-base registers by RTC (real-time clock) value
// RTC is number of seconds, since 01 January 00:00:00 2000
void __SyncTime()
{
    OSTick  rtcValue;
    OSSram* sram = __OSLockSram();

    if( __OSGetRTC(&rtcValue) == TRUE )
    {
        // counterBias can be negative value
        rtcValue += sram->counterBias;
        __OSSetTime( (OSTime)rtcValue * OS_TIMER_CLOCK );
    }

    __OSUnlockSram(0);
}

static int BS2State = 0;

// just wrapper..
0x813022c0()
{
    BS2State = BS2Mach();
}

// 81300A70
// located in __FILE__ = "BS2Mach.c"
int BS2Mach()
{
    static int state = 0;
    BOOL level = OSDisableInterrupts();

    switch(state)
    {
        case 0:
            [r13 - 0x7dc8] = 0x800030d4;
            state = 1;

        case 1:
            __OSGetSystemTime();
            ... some checks
            ... lot of mulhwu, subfe and other strange instructions :)
            ... lazy to find corresponding macro in os.h
            if(fail) break;
            state = 2;

        // Install DVD cover callback
        case 2:
            if([r13 - 0x7da8] == 0)
            {
                r3 = [r13 - 0x7dc8]
                [r3] = 0
                [r13 - 0x7dc4] = 0
                [r13 - 0x7dac] = 1
                DVDLowSetResetCoverCallback(0);
                DVDReset();
                [r13 - 0x7da8] = 1
                (s64)[r13 - 0x7d9c] = __OSGetSystemTime();
                break;
            }

            __OSGetSystemTime();
            bla bla bla
            another checks :)
            if(fail) break;

            DVDLowSetResetCoverCallback(0x813007d8);
            DVDReset();
            state = 3;

        // Read Disk information (ID)
        case 3:
            DVDReadDiskID(0x8145e620 + 64, 0x80000000, 0x813007e4);
            state = 4;            
            break;

        .
        .
        .
        .
        .
        .
        .

        // Leave immidiately ?
        case 16:
            break;

        default:
            OSHalt("BS2 ERROR >> UNKNOWN STATE");
    }

    OSRestoreInterrupts(level);

    return (DVDLowGetCoverStatus() == 1) ? 19 : step;
}

// 81301154
void BS2Menu()
{

    BS2InitAlloc();

}

static OSHeapHandler BS2Heap;

// 81307EA8
void BS2InitAlloc()
{
    u8  *arenaLo;
    u8  *arenaHi;
    u8  *arenaNew;

    arenaLo = OSGetArenaLo();
    arenaLo = (void *)OSRoundUp32B(arenaLo);
    arenaHi = OSGetArenaHi();
    arenaHi = (void *)OSRoundDown32B(arenaHi);

    arenaNew = OSInitAlloc(0x80800000, arenaHi, 2);
    OSSetArenaLo(arenaHi);

    BS2Heap = OSCreateHeap(arenaLo, arenaHi);
    OSSetCurrentHeap(BS2Heap);

    OSAddToHeap(BS2Heap, arenaNew, 0x81100000);

    BS2CheckAlloc();
}

// 81307F34
void BS2CheckAlloc()
{
    OSCheckHeap(BS2Heap);
}

// 81307F58
void *OSAlloc(long size)
{
    void *ptr;

    if((ptr = OSAlloc(size)) == 0)
    {
        OSPanic(?);
    }

    return ptr;
}


here is the MAP of BS2 libraries code :
---------------------------------------

813014C8    DEMOInit (*)
81307F58    OSAlloc (*)
813327BC    PPCMtmmcr0                      ;; PPC
813327C4    PPCMfpmc1
813327CC    PPCMtpmc1               
81332814    OSInit                          ;; OS
81332EF0    OSInitAlarm
81332F3C    OSCreateAlarm
81333688    OSAllocFromHeap
81333784    OSSetCurrentHeap
81333794    OSInitAlloc
81333804    OSCreateHeap
81333870    OSAddToHeap
813338D0    OSCheckHeap
813344C0    OSGetStackPointer
8133491C    OSReport
8133499C    OSPanic
81334AA4    PPCHalt
81334D4C    EXIImm                          ;; OS EXI (not standalone)
81335134    EXISync
813353C8    EXIProbeReset
8133570C    EXISelect               
81335838    EXIDeselect
81335D6C    EXILock
81335E60    EXIUnlock
81335F54    AD16Init
81336090    AD16WriteReg
813361B0    OSDisableInterrupts
813361C4    OSEnableInterrupts 
813361D8    OSRestoreInterrupts
81336DD8    __OSGetRTC
813372B0    __OSLockSram
81337658    __OSUnlockSram
813376A0    __OSSyncSram
813376B0    __OSCheckSram
81338504    OSInitThreadQueue
8133939C    OSGetTick
813393B8    __OSSetTime
8133943C    __OSGetSystemTime
8133963C    OSTicksToCalendarTime
8133AC50    DVDLowGetCoverStatus            ;; DVD
8133AB18    DVDLowReset
8133ABD4    DVDLowSetResetCoverCallback
8133B5F0    DVDInit                 
8133CD18    DVDReadDiskID
8133D0EC    DVDReset
8133DBE0    __VIInit                        ;; VI
8133DDC8    VIInit
8133E6C0    VIConfigure
8133F0B4    VIGetTvFormat
81343114    CARDInit                        ;; CARD
813480D4    GXInit                          ;; GX
81349148    GXInitFifoBase
81349230    GXSetCPUFifo
81349340    GXSetGPFifo
813494B8    __GXFifoInit
8134B0AC    __GXPEInit
8135A178    __div2i                         ;; LIBC
8135A394    __mod2i
8135B494    vprintf                 

(*) that function was slighty modified particulary for BS2 ?

note : European PAL IPL v. 1.0 was used.

-------------------------------------------------------------------------------

org -- kvzorganic@mail.ru

Thanks to tmbinc and titanik for their great work!

links :
GCDEV headquarters : http://www.gcdev.com, http://www.gcdev.com/forums

brief description of BS2 run-flow you can find in awesome Titanik's 'GAMECUBE
LOW-LEVEL INFO'.
