Espresso boot ROM

From WiiUBrew
Jump to navigation Jump to search

The Espresso includes a special boot ROM that checks all software it boots. It only boots software in the form of an Ancast Image.

Initialization

The Espresso boot ROM runs from address 0x00000100 where the PPC reset vector is located. It begins by setting up SPRs, mapping memory and jumping to the main routine:

// Clear MSR, TLB and TBU
MSR = 0
TBL = 0
TBU = 0

// Set HID0_BHT, HID0_BTIC, HID0_SGE and HID0_SPD
HID0 = 0x2A4

// Set HID4_SBE
HID4 |= 0x2000000

// Set a flag in BCR when in vWii mode
if ((SCR & 0x8000000) != 0 && (SCR & 0x4000000) != 0)
    BCR = ((BCR & 0x1FFFFFFF) | 0x20000000)
   
__isync();

// Clear all DBATs/IBATs
DBAT0U = 0
DBAT1U = 0
DBAT2U = 0
DBAT3U = 0
DBAT4U = 0
DBAT5U = 0
DBAT6U = 0
DBAT7U = 0
IBAT0U = 0
IBAT1U = 0
IBAT2U = 0
IBAT3U = 0
IBAT4U = 0
IBAT5U = 0
IBAT6U = 0
IBAT7U = 0

__isync();

// Clear all SRs
SR0 = 0
SR1 = 0
SR2 = 0
SR3 = 0
SR4 = 0
SR5 = 0
SR6 = 0
SR7 = 0
SR8 = 0
SR9 = 0
SR10 = 0
SR11 = 0
SR12 = 0
SR13 = 0
SR14 = 0
SR15 = 0

__sync();

// Flush L2CR with a dummy read
L2CR = L2CR

__sync();

// Set L2CR_L2I
L2CR |= 0x200000

// Wait for L2CR_L2IP to be cleared
while ((L2CR & 0x1) != 0);

// Clear L2CR_L2I
L2CR &= 0xFFDFFFFF

// Wait for L2CR_L2IP to be cleared
while ((L2CR & 0x1) != 0);

// Set HID0_DCFI and HID0_ICFI
HID0 |= 0xC00

__isync();

// Configure DBAT0
// Physical address is 0
// Virtual address is 0
// Mapped as read only, cache inhibit, user valid and supervisor valid
DBAT0L = 0x21
DBAT0U = 0x3

__isync();

// Configure IBAT0
// Physical address is 0
// Virtual address is 0
// Mapped as read only, cache inhibit, user valid and supervisor valid
IBAT0L = 0x21
IBAT0U = 0x3

__isync();

// Configure DBAT1
// Physical address is 0xC320000
// Virtual address is 0xC320000
// Mapped as read only, cache inhibit, user valid and supervisor valid
DBAT1L = 0xC320021
DBAT1U = 0xC320003

__isync();

// Configure DBAT2
// Physical address is 0xE0000000
// Virtual address is 0xE0000000
// Mapped as read write, user valid and supervisor valid
DBAT2L = 0xE0000002
DBAT2U = 0xE0000003

__isync();

// Configure DBAT4
// Physical address is 0x16C0000
// Virtual address is 0x20000
// Mapped as read write, user valid and supervisor valid
DBAT4L = 0x16C0002
DBAT4U = 0x20003

__isync();

// Configure IBAT4
// Physical address is 0x16C0000
// Virtual address is 0x20000
// Mapped as read write, user valid and supervisor valid
IBAT0L = 0x16C0002
IBAT0U = 0x20003

__isync();

// Clear DBAT6
DBAT6L = 0
DBAT6U = 0

__isync();

// Configure DBAT7
// Physical address is 0x16E0000
// Virtual address is 0xC16E0000
// Mapped as read write, guarded, cache inhibit, user valid and supervisor valid
DBAT7L = 0x16E002A
DBAT7U = 0xC16E0003

__isync();

// Set MSR_DR and MSR_IR
MSR |= 0x30

__isync();

// Clear L2CR_L2TS, L2CR_L2I, L2CR_L2DO and set L2CR_L2E
L2CR = ((L2CR | 0x80000000) & 0xFF9BFFFF)

// Set HID0_ICE and HID0_DCE
HID0 |= 0xC000

// Set HID2_LCE
HID2 |= 0x10000000

u32 index = 0xE0000000;
u32 size = 0x200;

// Set to zero data cache for the 0xE0000000 region
while (size > 0)
{
    __dcbzl(0, index);
    index += 0x20;
    size -= 0x1;
}

// Configure DBAT5
// Physical address is 0
// Virtual address is 0xF0000000
// Mapped as read write, user valid and supervisor valid
DBAT5L = 0x2
DBAT5U = 0xF0000003

__isync();

index = 0xF0000100;
size = 0x7;

// Test cache flushing
while (size > 0)
{
    *(u32 *)index = 0x48000000;
    
    __dcbf(0, index);
    __isync();
    __sync();
    
    if (*(u32 *)index != 0x48000000)
    {
        // Deadlock
        sub_E0();
    }
        
    index += 0x100;
    size -= 0x1;
}

// Clear DBAT5
DBAT5L = 0
DBAT5U = 0

__isync();

SRR0 = 0x758
SRR1 = MSR

// Set LR to a deadlock
LR = sub_E0

// Execution will jump to 0x758
return;

Main

This is the main function of the Espresso boot ROM. It is located at address 0x00000758 (where the initialization routine jumps to) and is responsible for initializing registers and memory, processing the PPC ancast image and running it.

*(u32 *)0xC16FFFFC = 0;
*(u32 *)0xC16FFFF8 = 0;
*(u32 *)0xC16FFFF4 = 0;
*(u32 *)0xC16FFFF0 = 0;

// Clear all GPRs and set the TOC and SDA addresses
InitializeRegisters();

// Initialize and map virtual memory
InitializeMemory();

// Load, verify and decrypt the ancast image
u32 entry_addr = LoadImg();

// Cleanup and jump to the loaded image
Finish(entry_addr);

// Execution will jump to a deadlock
return;

InitializeMemory

The Espresso boot ROM copies and clears several memory regions.

// Copy memory regions:
// Source address 0xB0C, destination address 0x20000, size 0x2D58
// Source address 0x3864, destination address 0xE0000000, size 0x6A0
// Source address 0x3F04, destination address 0xE00006A0, size 0xD0
// Source address 0x3FD4, destination address 0xE0000780, size 0xC
for (int i = 0; mem_copy_array[i].size > 0; i++)
{
    u32 src_addr = mem_copy_array[i].src_addr;
    u32 dst_addr = mem_copy_array[i].dst_addr;
    u32 size = mem_copy_array[i].size;

    if (src_addr != dst_addr)
    {
        memcpy(dst_addr, src_addr, size);
        
        if (dst_addr == 0x20000)
        {
            // Set to zero data cache for the 0x24000 region
            sub_5A4();
            
            // Invalidate instruction cache for the given range
            sub_604(dst_addr, size);
            
            if (size)
            {
                if (memcmp(dst_addr, src_addr, size))
                {
                    *(u32 *)0xC16FFFFC = 0x1000000;
                    while (1);
                }
            }
        }
    }
}

// Clear memory regions:
// Address 0xE00007A0, size 0x18
// Address 0xE00007C0, size 0x2E4
for (int i = 0; mem_clear_array[i].size > 0; i++)
{
    memset(mem_clear_array[i].addr, 0, mem_clear_array[i].size);
}

return;

LoadImg

The Espresso boot ROM loads, verifies and decrypts the PPC ancast image.

u8 aes_key[0x10] = {0};
u32 aes_key_addr = 0;
u32 ecdsa_key_addr = 0;
u32 img_addr = 0;
u32 img_state = 0;
ImgHeader img_header;
u8 img_hash[0x14] = {0};
u32 is_evaluation = 0;
u32 time_fw_verify_start = 0;
u32 time_fw_verify_end = 0;
u32 time_fw_decrypt_start = 0;
u32 time_fw_decrypt_end = 0;

// Does nothing, just returns
sub_784();

// Check if SCR bits 30 and 31 are set
if ((GetSCR() & 0xF0000000) == 0xC0000000)
{
    // Set an error if bit 26 is not set
    if ((GetSCR() & 0x2000000) == 0)
    {
      SetState(0x11000000);
      
      // Deadlock
      sub_20000();
    }
    
    // Use a secret static key
    memcpy(aes_key, aes_static_key, 0x10);
    aes_key_addr = &aes_key;
    
    SetImgState(0x80);
    
    // Use production ECDSA key
    ecdsa_key_addr = &ecdsa_prod_key;
    
    SetImgState(0x20000);
}
else
{
    // Read the fuse type from eFuses
    u32 fuse_type = *(u32 *)(efuse_addr + 0x3C);
    
    // Set SCR bit 28
    SetSCR(GetSCR() | 0x10000000);
    
    if ((fuse_type & 0x4000000) != 0)
    {
        if (IsVWii())
        {
            // Copy the vWii AES key from eFuses
            memcpy(aes_key, efuse_addr + 0x10, 0x10);
            
            SetImgState(2);
        }
        else
        {
            // Copy the WiiU AES key from eFuses
            memcpy(aes_key, efuse_addr, 0x10);
            
            SetImgState(1);
        }
        
        aes_key_addr = &aes_key;
        
        // Read the fuse type from eFuses
        fuse_type = *(u32 *)(efuse_addr + 0x3C);
        
        if ((fuse_type & 0x18000000) == 0x8000000)
        {
            // Use development ECDSA key
            ecdsa_key_addr = &ecdsa_dev_key;
            
            SetImgState(0x10000);
        }
        else if ((fuse_type & 0x18000000) == 0x10000000)
        {
            // Use production ECDSA key
            ecdsa_key_addr = &ecdsa_prod_key;
            
            SetImgState(0x20000);
        }
        else
        {
            SetState(0x12000000);
      
            // Deadlock
            sub_20000();
        }
    }
    else
    {
        aes_key_addr = 0;
        ecdsa_key_addr = 0;
        is_evaluation = 1;
    }
}

if (IsVWii())
{
    img_addr = 0x1330000;
    
    if ((GetHID1() & 0x8000000) != 0)
        SetImgState(0x400);
    else
        SetImgState(0x200);
}
else
{
    img_addr = 0x8000000;
    
    SetImgState(0x100);
}
  
if (aes_key_addr)
{    
    // Get the current time
    time_fw_verify_start = GetTB();
    
    // Load the ancast image over DMA
    DmaLoad(dma_addr, img_addr, 0);
    DmaBarrier(0);

    // Copy the ancast header
    memcpy(&img_header, dma_addr, 0x100);
    
    // Verify the ancast header's ECDSA signature
    if (!EcdsaVerify(ecdsa_key_addr, (u8 *)&img_header.reserved4, 0x60, img_header.signature))
    {
        SetState(0x21000000);
        
        // Deadlock
        sub_20000();
    }
    
    // Ensure 2 bytes at offset 0xA0 are set to 0
    if (img_header.reserved4 != 0)
    {
        SetState(0x22000000);
        
        // Deadlock
        sub_20000();
    }
    
    // Ensure bytes at offsets 0xA2 and 0xA3 are set to 0
    if ((img_header.reserved5 != 0) || (img_header.reserved6 != 0))
    {
        SetState(0x23000000);
        
        // Deadlock
        sub_20000();
    }
    
    // Ensure 0x3C bytes at offset 0xC4 are set to 0
    for (int i = 0; i < 0x3C; i++)
    {
        if (img_header.reserved7[i] != 0)
        {
            SetState(0x24000000);
        
            // Deadlock
            sub_20000();
        }
    }
    
    if ((img_state & 0xFF) == 0x80)
    {
        if (img_header.targetDevice != 0x14)
        {
            SetState(0x29000000);
        
            // Deadlock
            sub_20000();
        }
    }
    else
    {
        if (img_header.targetDevice == 0x11)
        {
            if ((img_state & 0xFF00) != 0x100)
            {
                SetState(0x25000000);
        
                // Deadlock
                sub_20000();
            }
        }
        else if (img_header.targetDevice == 0x12)
        {
            if ((img_state & 0xFF00) != 0x400)
            {
                SetState(0x25000000);
        
                // Deadlock
                sub_20000();
            }
        }
        else if (img_header.targetDevice == 0x13)
        {
            if ((img_state & 0xFF00) != 0x200)
            {
                SetState(0x25000000);
        
                // Deadlock
                sub_20000();
            }
        }
        else
        {
            SetState(0x25000000);
        
            // Deadlock
            sub_20000();
        }
    }
    
    if (img_header.consoleType == 1)
    {
        if ((img_state & 0xFF0000) != 0x10000)
        {
            SetState(0x26000000);
    
            // Deadlock
            sub_20000();
        }
    }
    else if (img_header.consoleType == 2)
    {
        
        if ((img_state & 0xFF0000) != 0x20000)
        {
            SetState(0x26000000);
    
            // Deadlock
            sub_20000();
        }
    }
    else
    {
        SetState(0x26000000);

        // Deadlock
        sub_20000();
    }
    
    u32 img_blk_count = (img_header.imgSize >> 0xC);
    
    if (!img_blk_count)
    {
        SetState(0x27000000);

        // Deadlock
        sub_20000();
    }
    else
    {
        u32 img_offset = img_addr + 0x100;
        
        // Initialize the SHA1 context
        Sha1Init(&sha1_ctx);
        
        // Load the ancast image and update the SHA1 context
        for (int i = 0; i < img_blk_count; i++)
        {
            DmaLoad(dma_addr + ((i << 12) & 0x1000), img_offset, 0);
            Sha1Update(&sha1_ctx, dma_addr + ((i << 12) & 0x1000), 0x1000);
            DmaBarrier(0);
            img_offset += 0x1000;
        }
        
        // Calculate the ancast image's hash
        Sha1Final(&sha1_ctx, img_hash);
        
        // Compare the calculated hash with the expected one
        if (memcmp(img_header.imgHash, img_hash, 0x14))
        {
            SetState(0x28000000);

            // Deadlock
            sub_20000();
        }
        else
        {
            // Get the current time
            time_fw_verify_end = GetTB(); 
            
            // Save the time spent verifying the image
            *(u32 *)0xC16FFFF8 = time_fw_verify_end - time_fw_verify_start;
            
            if (!img_blk_count)
            {
                // Deadlock
                sub_20000();
            }
            else
            {
                // Get the current time
                time_fw_decrypt_start = GetTB();
                
                // Initialize the AES context
                AesCtxIni(&aes_ctx, aes_key_addr, 0x10, aes_iv, 0);
                
                img_offset = img_addr + 0x100;
                
                // Load, decrypt and store the ancast image
                for (int i = 0; i < img_blk_count; i++)
                {
                    DmaLoad(dma_addr + ((i << 12) & 0x1000), img_offset, 0);
                    AesDecrypt(&aes_ctx, dma_addr + ((i << 12) & 0x1000), 0x1000, dma_addr + ((i << 12) & 0x1000));
                    DmaStore(img_offset, dma_addr + ((i << 12) & 0x1000), 0);
                    DmaBarrier(0);
                    img_offset += 0x1000;
                }
                
                // Does nothing, just returns
                sub_20950();
                
                // Get the current time
                time_fw_decrypt_end = GetTB(); 
                
                // Save the time spent decrypting the image
                *(u32 *)0xC16FFFF4 = time_fw_decrypt_end - time_fw_decrypt_start;
            }
        }
    }
}

return img_addr + 0x100;

Finish

The Espresso boot ROM cleans up and jumps to the entrypoint address returned by LoadImg minus 4 bytes.

// Load state from MSR
u32 state = MSR

if (!is_evaluation)
    SCR &= 0xCFFFFFFF

// Clear 0x20000 region
memset(0x20000, 0, 0x4000);

// Set to zero data cache for the 0x24000 region
sub_5A4();

// Clear 0xE0000000 region
memset(0xE0000000, 0, 0x4000);

// Invalidate cache for the 0xE0000000 region and clear HID2_LCE
sub_638();

// Clear DBAT1, DBAT2, DBAT4 and IBAT4
sub_660();

if (!IsVWii())
{
    // Configure DBAT3 and IBAT3 for WiiU mode
    // Physical address is 0x8000000
    // Virtual address is 0x8000000
    // Mapped as read only, user valid, supervisor valid and block size is 2MB
    sub_788();
    
    if (entry_addr != 0x8000100)
    {
        *(u32 *)0xC16FFFFC |= 0x31000000;
        while (1);
    }
    else
        state |= 0x40;
}
else
{
    // Configure DBAT3 and IBAT3 for vWii mode
    // Physical address is 0x1000000
    // Virtual address is 0x1000000
    // Mapped as read only, user valid, supervisor valid and block size is 8MB
    sub_7B4();
    
    if (entry_addr != 0x1330100)
    {
        *(u32 *)0xC16FFFFC |= 0x31000000;
        while (1);
    }
}

// Store TB
*(u32 *)0xC16FFFF0 = TB

// Clear TBL and TBU
sub_1E8();

// Clear L2CR_L2TS and do a global invalidate
sub_69C();

// Clear HID0_DCE and HID0_ICE
HID0 &= 0xFFFF3FFF

__isync();

// Set L2CR_L2E and clear L2CR_L2I 
sub_7E0();

// Set HID0_DCE and HID0_ICE
HID0 |= 0xFFFF3FFF

__isync();

// Set HID0_DCFI and HID0_ICFI  
HID0 |= 0xC00

__isync();
__sync();
  
// Write the bootrom disable instruction and flush the cache  
u32 index = ((entry_addr - 0x4) & 0xFFFFFFE0);
*(u32 *)(entry_addr - 0x4) = 0x7C73EBA6;
  
__dcbf(0, index);
__isync();
__sync();
  
if (*(u32 *)(entry_addr - 0x4) != 0x7C73EBA6)
{
    *(u32 *)0xC16FFFFC |= 0x31000000;
    while (1);
}
    
*(u32 *)0xC16FFFFC |= 0x80000000;

SRR0 = (entry_addr - 0x4)
SRR1 = state

// Copy SCR to R3 as it will be used by the bootrom disable instruction
R3 = ((SCR | 0x80000000) & 0xBFFFFFFF)

// Execution will jump to entry_addr - 4
return;

BootInfo

This is a structure mapped by the Espresso boot ROM to address 0xC16FFFF0.

Offset Size Description
0x0 0x4 BootMainTime
0x4 0x4 BootDecryptTime
0x8 0x4 BootVerifyTime
0xC 0x4 BootState