In memory of Ben “bushing” Byer, who passed away on Monday, February 8th, 2016.

Difference between revisions of "Boot0"

From WiiUBrew
Jump to navigation Jump to search
(Fix boot1 from SD card info)
Line 4: Line 4:
 
What follows are general descriptions and pseudo-code that illustrates the several sub-stages of the Wii U's boot0.
 
What follows are general descriptions and pseudo-code that illustrates the several sub-stages of the Wii U's boot0.
  
==Initialization==
+
== Initialization ==
 
boot0 runs from address 0xFFFF0000 where the ARM exception vectors are located. At this point, all exception vectors point to themselves (deadlock) except for the reset vector.
 
boot0 runs from address 0xFFFF0000 where the ARM exception vectors are located. At this point, all exception vectors point to themselves (deadlock) except for the reset vector.
 
Following the execution into the reset vector, the following takes place:
 
Following the execution into the reset vector, the following takes place:
Line 25: Line 25:
 
This behavior could also be observed, to some extent, in the old Wii's bootloader.
 
This behavior could also be observed, to some extent, in the old Wii's bootloader.
  
==Setup==
+
== Setup ==
 
Right after boot0 copies itself over to SRAM, it does the following:
 
Right after boot0 copies itself over to SRAM, it does the following:
 
  // Invalidate ICache
 
  // Invalidate ICache
Line 56: Line 56:
 
Essentially, sets up it's own stack and jumps to boot0's main function.
 
Essentially, sets up it's own stack and jumps to boot0's main function.
  
==Main==
+
== Main ==
 
This is the bulk of the first-stage bootloader. During the main function's execution, boot0 will send different signals to debug ports via GPIO.
 
This is the bulk of the first-stage bootloader. During the main function's execution, boot0 will send different signals to debug ports via GPIO.
 
These signals can be used to represent error codes or mark different execution sub-stages.
 
These signals can be used to represent error codes or mark different execution sub-stages.
  
 
===Start===
 
===Start===
boot0 reads and saves the value from register LT_TIMER.
+
boot0 reads and saves the value from register HW_TIMER.
 
  // Get timer value
 
  // Get timer value
  u32 time_now = *(u32 *)LT_TIMER;
+
  u32 time_now = *(u32 *)HW_TIMER;
 
   
 
   
 
  // Set start time
 
  // Set start time
Line 69: Line 69:
  
 
===Stage 0x00===
 
===Stage 0x00===
boot0 sets a flag in LT_BOOT0 and configures debug ports' GPIOs.
+
boot0 sets a flag in HW_SPARE1 and configures debug ports' GPIOs.
 
  // Send debug mark
 
  // Send debug mark
 
  SendGPIODebugOut(0x00);
 
  SendGPIODebugOut(0x00);
 
   
 
   
  u32 boot0_val = *(u32 *)LT_BOOT0;
+
  u32 spare1_val = *(u32 *)HW_SPARE1;
 
   
 
   
  // Set something in LT_BOOT0
+
  // Set something in HW_SPARE1
  *(u32 *)LT_BOOT0 = boot0_val | 0xC0;
+
  *(u32 *)HW_SPARE1 = spare1_val | 0xC0;
 
   
 
   
 
  // Enable GPIO for debug ports
 
  // Enable GPIO for debug ports
  u32 gpio_enable_val = *(u32 *)LT_GPIO_ENABLE;
+
  u32 gpio_enable_val = *(u32 *)HW_GPIO_ENABLE;
  *(u32 *)LT_GPIO_ENABLE = gpio_enable_val | 0x00FF0000;
+
  *(u32 *)HW_GPIO_ENABLE = gpio_enable_val | 0x00FF0000;
 
   
 
   
 
  // Set direction to output
 
  // Set direction to output
  u32 gpio_dir_val = *(u32 *)LT_GPIO_DIR;
+
  u32 gpio_dir_val = *(u32 *)HW_GPIO_DIR;
  *(u32 *)LT_GPIO_DIR = gpio_dir_val | 0x00FF0000;
+
  *(u32 *)HW_GPIO_DIR = gpio_dir_val | 0x00FF0000;
  
===Stage 0x01===
+
=== Stage 0x01 ===
 
boot0 sets something for memory swap.
 
boot0 sets something for memory swap.
 
  // Send debug mark
 
  // Send debug mark
Line 92: Line 92:
 
   
 
   
 
  // Set memory swap
 
  // Set memory swap
  *(u32 *)LT_MEMIRR = 0x07;
+
  *(u32 *)HW_SRNPROT = 0x07;
  
===Stage 0x02===
+
=== Stage 0x02 ===
 
boot0 initializes the AES engine.
 
boot0 initializes the AES engine.
 
  // Send debug mark
 
  // Send debug mark
Line 111: Line 111:
 
  *(u32 *)AES_DEST = 0;
 
  *(u32 *)AES_DEST = 0;
  
===Stage 0x03===
+
=== Stage 0x03 ===
 
boot0 initializes the SHA-1 engine.
 
boot0 initializes the SHA-1 engine.
 
  // Send debug mark
 
  // Send debug mark
Line 124: Line 124:
 
  *(u32 *)SHA_SRC = 0;
 
  *(u32 *)SHA_SRC = 0;
  
===Stage 0x04===
+
=== Stage 0x04 ===
 
boot0 sets something in LT_COMPAT_AHB and enables OTP reading.
 
boot0 sets something in LT_COMPAT_AHB and enables OTP reading.
 
  // Send debug mark
 
  // Send debug mark
Line 130: Line 130:
 
   
 
   
 
  // Set something in AHB compat
 
  // Set something in AHB compat
  u32 ahb_val = *(u32 *)LT_COMPAT_AHB;
+
  u32 ahb_val = *(u32 *)LT_AHBCMPT;
  *(u32 *)LT_COMPAT_AHB = (ahb_val & 0xFFFFF3FF) | 0xC00;
+
  *(u32 *)LT_AHBCMPT = (ahb_val & 0xFFFFF3FF) | 0xC00;
 
   
 
   
 
  // Enable OTP reading
 
  // Enable OTP reading
 
  SetOTPReadCommand();
 
  SetOTPReadCommand();
  
===Stages 0x05, 0x06 and 0x07===
+
=== Stages 0x05, 0x06 and 0x07 ===
 
boot0 asserts some resets and enables EXI.
 
boot0 asserts some resets and enables EXI.
 
These three stages are all merged together and the only signal sent to the debug ports is effectively 0x05.
 
These three stages are all merged together and the only signal sent to the debug ports is effectively 0x05.
Line 143: Line 143:
 
   
 
   
 
  // Assert RSTB_IOPI
 
  // Assert RSTB_IOPI
  u32 resets = *(u32 *)LT_RESETS;
+
  u32 resets = *(u32 *)HW_RSTB;
  *(u32 *)LT_RESETS = resets | 0x80000;
+
  *(u32 *)HW_RSTB = resets | 0x80000;
 
   
 
   
 
  // Assert RSTB_IOMEM
 
  // Assert RSTB_IOMEM
  u32 resets = *(u32 *)LT_RESETS;
+
  u32 resets = *(u32 *)HW_RSTB;
  *(u32 *)LT_RESETS = resets | 0x40000;
+
  *(u32 *)HW_RSTB = resets | 0x40000;
 
   
 
   
 
  // Enable EXI
 
  // Enable EXI
  u32 exi_ctrl = *(u32 *)LT_EXICTRL;
+
  u32 aip_prot = *(u32 *)HW_AIP_PROT;
  *(u32 *)LT_EXICTRL = exi_ctrl | 0x01;
+
  *(u32 *)HW_AIP_PROT = aip_prot | 0x01;
  
===Stage 0x08===
+
=== Stage 0x08 ===
 
During this stage boot0 reads all it needs from the OTP.
 
During this stage boot0 reads all it needs from the OTP.
 
  // Send debug mark
 
  // Send debug mark
Line 181: Line 181:
 
  // Setup devices' strength
 
  // Setup devices' strength
 
  u32 iostrength_flags = *(u32 *)0x0D41375C;
 
  u32 iostrength_flags = *(u32 *)0x0D41375C;
  u32 iostrength_ctrl0_val = *(u32 *)LT_IOSTRENGTH_CTRL0;
+
  u32 iostrength_ctrl0_val = *(u32 *)HW_IOSTRCTRL0;
  u32 iostrength_ctrl1_val = *(u32 *)LT_IOSTRENGTH_CTRL1;
+
  u32 iostrength_ctrl1_val = *(u32 *)HW_IOSTRCTRL1;
 
   
 
   
  if (((iostrength_flags >> 0x0F) & 0x01) != 0)
+
  if (((iostrength_flags >> 0x0F) & 0x01) != 0) {
{
 
 
     iostrength_ctrl0_val &= 0xFFFC7FFF;
 
     iostrength_ctrl0_val &= 0xFFFC7FFF;
 
     iostrength_ctrl0_val |= ((iostrength_flags << 0x11) >> 0x1D) << 0x0F;
 
     iostrength_ctrl0_val |= ((iostrength_flags << 0x11) >> 0x1D) << 0x0F;
 
  }
 
  }
 
   
 
   
  *(u32 *)LT_IOSTRENGTH_CTRL0 = iostrength_ctrl0_val;
+
  *(u32 *)HW_IOSTRCTRL0 = iostrength_ctrl0_val;
 
   
 
   
  if (((iostrength_flags >> 0x0F) & 0x01) != 0)
+
  if (((iostrength_flags >> 0x0F) & 0x01) != 0) {
{
 
 
     iostrength_ctrl1_val &= 0xFFFC7FFF;
 
     iostrength_ctrl1_val &= 0xFFFC7FFF;
 
     iostrength_ctrl1_val |= (iostrength_flags & 0x07) << 0x0F;
 
     iostrength_ctrl1_val |= (iostrength_flags & 0x07) << 0x0F;
 
  }
 
  }
 
   
 
   
  if (((iostrength_flags >> 0x07) & 0x01) != 0)
+
  if (((iostrength_flags >> 0x07) & 0x01) != 0) {
{
 
 
     iostrength_ctrl1_val &= 0xFFE3FFFF;
 
     iostrength_ctrl1_val &= 0xFFE3FFFF;
 
     iostrength_ctrl1_val |= ((iostrength_flags << 0x19) >> 0x1D) << 0x12;
 
     iostrength_ctrl1_val |= ((iostrength_flags << 0x19) >> 0x1D) << 0x12;
 
  }
 
  }
 
   
 
   
  if (((iostrength_flags >> 0x0B) & 0x01) != 0)
+
  if (((iostrength_flags >> 0x0B) & 0x01) != 0) {
{
 
 
     iostrength_ctrl1_val &= 0xFFFF8FFF;
 
     iostrength_ctrl1_val &= 0xFFFF8FFF;
 
     iostrength_ctrl1_val |= ((iostrength_flags << 0x15) >> 0x1D) << 0x0C;
 
     iostrength_ctrl1_val |= ((iostrength_flags << 0x15) >> 0x1D) << 0x0C;
 
  }
 
  }
 
   
 
   
  if (((iostrength_flags >> 0x13) & 0x01) != 0)
+
  if (((iostrength_flags >> 0x13) & 0x01) != 0) {
{
 
 
     iostrength_ctrl1_val &= 0xFFFF8FFF;
 
     iostrength_ctrl1_val &= 0xFFFF8FFF;
 
     iostrength_ctrl1_val |= ((iostrength_flags << 0x0D) >> 0x1D) << 0x15;
 
     iostrength_ctrl1_val |= ((iostrength_flags << 0x0D) >> 0x1D) << 0x15;
 
  }
 
  }
 
   
 
   
  *(u32 *)LT_IOSTRENGTH_CTRL1 = iostrength_ctrl1_val;
+
  *(u32 *)HW_IOSTRCTRL1 = iostrength_ctrl1_val;
  
===Stage 0x0A===
+
=== Stage 0x0A ===
 
boot0 sets the SEEPROM's pulse length and configures the SEEPROM GPIOs.
 
boot0 sets the SEEPROM's pulse length and configures the SEEPROM GPIOs.
 
  // Send debug mark
 
  // Send debug mark
Line 235: Line 230:
 
  Set_SEEPROM_GPIO();
 
  Set_SEEPROM_GPIO();
  
===Stage 0x0B===
+
=== Stage 0x0B ===
 
boot0 analyses the OTP security level flag and decides which keys to use.
 
boot0 analyses the OTP security level flag and decides which keys to use.
 
  // Send debug mark
 
  // Send debug mark
Line 249: Line 244:
 
  if (sec_lvl_flag == 0x00000000)
 
  if (sec_lvl_flag == 0x00000000)
 
     set_empty_aes_keys();
 
     set_empty_aes_keys();
  else
+
  else {
{
 
 
     // Disable boot1 AES key access
 
     // Disable boot1 AES key access
 
     u32 otpprot_val = *(u32 *)LT_OTPPROT;
 
     u32 otpprot_val = *(u32 *)LT_OTPPROT;
Line 264: Line 258:
 
  }
 
  }
  
===Stage 0x0C===
+
=== Stage 0x0C ===
 
boot0 generates a CRC32 table in it's stack.
 
boot0 generates a CRC32 table in it's stack.
 
  // Send debug mark
 
  // Send debug mark
Line 273: Line 267:
 
  CRC32_Gen();
 
  CRC32_Gen();
  
===Stage 0x0D===
+
=== Stage 0x0D ===
 
boot0 uses the SEEPROM key to decrypt SEEPROM data related to the boot process.
 
boot0 uses the SEEPROM key to decrypt SEEPROM data related to the boot process.
 
  // Send debug mark
 
  // Send debug mark
Line 290: Line 284:
 
  SEEPROM_Read(0x1E, 0x0D4137AC, 0x01);
 
  SEEPROM_Read(0x1E, 0x0D4137AC, 0x01);
  
===Stage 0x0E===
+
=== Stage 0x0E ===
 
boot0 validates the data decrypted from the SEEPROM at offset 0x1C0 in the previous stage using CRC32.
 
boot0 validates the data decrypted from the SEEPROM at offset 0x1C0 in the previous stage using CRC32.
 
This data is used to set miscellaneous settings during boot0's execution.
 
This data is used to set miscellaneous settings during boot0's execution.
Line 326: Line 320:
 
   
 
   
 
  // Factory mode doesn't need boot1's data
 
  // Factory mode doesn't need boot1's data
  if (!aes_seeprom_key)
+
  if (!aes_seeprom_key) {
{
 
 
     boot1_version = 0;
 
     boot1_version = 0;
 
     boot1_sector = 0;
 
     boot1_sector = 0;
 
  }
 
  }
  
===Stage 0x0F===
+
=== Stage 0x0F ===
 
boot0 validates the data decrypted from the SEEPROM at offsets 0x1D0 and 0x1E0 in the previous stage using CRC32.
 
boot0 validates the data decrypted from the SEEPROM at offsets 0x1D0 and 0x1E0 in the previous stage using CRC32.
 
This data is used to determine boot1's version and sector inside the NAND.
 
This data is used to determine boot1's version and sector inside the NAND.
Line 363: Line 356:
 
   
 
   
 
  // Check if seeprom_1C_00 is not 0x400
 
  // Check if seeprom_1C_00 is not 0x400
  if ((seeprom_1C_00 & 0x3FF) != 0)
+
  if ((seeprom_1C_00 & 0x3FF) != 0) {
{
 
 
     // Store at 0x0D414200
 
     // Store at 0x0D414200
 
     sub_D41203C(seeprom_1C_00 & 0x3FF);
 
     sub_D41203C(seeprom_1C_00 & 0x3FF);
 
  }
 
  }
  
===Stages 0x10, 0x11 and 0x12===
+
=== Stages 0x10, 0x11 and 0x12 ===
 
boot0 configures the Starbuck's clock multiplier.
 
boot0 configures the Starbuck's clock multiplier.
 
These stages are optional and only execute if the 2-byte flag read from the SEEPROM at offset 0x1C0 translates to a negative value. The three stages are merged together and the only signal sent to the debug ports is effectively 0x10.
 
These stages are optional and only execute if the 2-byte flag read from the SEEPROM at offset 0x1C0 translates to a negative value. The three stages are merged together and the only signal sent to the debug ports is effectively 0x10.
Line 375: Line 367:
 
  SendGPIODebugOut(0x10);
 
  SendGPIODebugOut(0x10);
 
   
 
   
  // Allow IRQ 12 (AHBLT)
+
  // Allow IRQ 12 (LT)
  *(u32 *)LT_INTMR_AHBLT_ARM = 0x1000;
+
  *(u32 *)LT_ARMIRQMASKLT = 0x1000;
 
 
 
 
 
  // Turn IOP2X on?
 
  // Turn IOP2X on?
Line 384: Line 376:
 
  sub_D4132E8();
 
  sub_D4132E8();
 
 
 
 
  // Disable IRQ 12 (AHBLT)
+
  // Disable IRQ 12 (LT)
  *(u32 *)LT_INTMR_AHBLT_ARM = 0;
+
  *(u32 *)LT_ARMIRQMASKLT = 0;
  
===Stage 0x13===
+
=== Stage 0x13 ===
 
boot0 analyses the remaining data read from SEEPROM at offset 0x1C0 and uses it to configure the NAND_CONFIG and NAND_BANK registers. It then initializes the NAND engine.
 
boot0 analyses the remaining data read from SEEPROM at offset 0x1C0 and uses it to configure the NAND_CONFIG and NAND_BANK registers. It then initializes the NAND engine.
 
  // Send debug mark
 
  // Send debug mark
Line 406: Line 398:
 
  u32 result = sub_D411564(boot1_isSLC);
 
  u32 result = sub_D411564(boot1_isSLC);
  
===Stage 0x14===
+
=== Stage 0x14 ===
 
boot0 checks if it's start time is valid (must be 0, otherwise boot0 throws an error) and saves the current time (in order to track how long boot1's reading took).
 
boot0 checks if it's start time is valid (must be 0, otherwise boot0 throws an error) and saves the current time (in order to track how long boot1's reading took).
 
This stage only executes if NAND engine's initialization was successful.
 
This stage only executes if NAND engine's initialization was successful.
Line 419: Line 411:
 
  u32 boot1_read_time = *(u32 *)LT_TIMER;
 
  u32 boot1_read_time = *(u32 *)LT_TIMER;
  
===Stages 0x15, 0x16 and 0x17===
+
=== Stages 0x15, 0x16 and 0x17 ===
 
boot0 flushes AHB memory, reads boot1's ancast header from NAND and checks the boot1's image size by looking into the respective field inside the header. If the size is valid (must not exceed 0xF800, so it doesn't overflow boot1's memory region), boot0 then proceeds to read the full boot1's image from NAND into address 0x0D400000:
 
boot0 flushes AHB memory, reads boot1's ancast header from NAND and checks the boot1's image size by looking into the respective field inside the header. If the size is valid (must not exceed 0xF800, so it doesn't overflow boot1's memory region), boot0 then proceeds to read the full boot1's image from NAND into address 0x0D400000:
 
Finally, boot0 calculates how long this operation took and stores this value for the IOS-MCP to read later on.
 
Finally, boot0 calculates how long this operation took and stores this value for the IOS-MCP to read later on.
Line 465: Line 457:
 
  // Calculate how long it took to
 
  // Calculate how long it took to
 
  // read boot1 (MCP later reads this)
 
  // read boot1 (MCP later reads this)
  u32 time_now = *(u32 *)LT_TIMER;
+
  u32 time_now = *(u32 *)HW_TIMER;
 
  *(u32 *)0x0D417FE4 = time_now - boot1_read_time;
 
  *(u32 *)0x0D417FE4 = time_now - boot1_read_time;
  
===Stage 0x18===
+
=== Stage 0x18 ===
 
boot0 checks if boot1 is encrypted or not (boot1 is not encrypted in factory mode).
 
boot0 checks if boot1 is encrypted or not (boot1 is not encrypted in factory mode).
 
This stage only executes if NAND engine's initialization was successful.
 
This stage only executes if NAND engine's initialization was successful.
Line 478: Line 470:
 
     goto skip_boot1_decrypt;
 
     goto skip_boot1_decrypt;
  
===Stage 0x19===
+
=== Stage 0x19 ===
 
boot0 verifies boot1's hash (SHA-1) and signature (RSA).
 
boot0 verifies boot1's hash (SHA-1) and signature (RSA).
 
This stage only executes if NAND engine's initialization was successful and if factory mode is not enabled.
 
This stage only executes if NAND engine's initialization was successful and if factory mode is not enabled.
Line 491: Line 483:
 
     throw_error();
 
     throw_error();
  
===Stage 0x1A===
+
=== Stage 0x1A ===
 
boot0 decrypts boot1 (using the AES engine) in place.
 
boot0 decrypts boot1 (using the AES engine) in place.
 
This stage only executes if NAND engine's initialization was successful and factory mode is not enabled.
 
This stage only executes if NAND engine's initialization was successful and factory mode is not enabled.
Line 500: Line 492:
 
  AES_Decrypt(boot1_key, 0x0D400000, image_blocks);
 
  AES_Decrypt(boot1_key, 0x0D400000, image_blocks);
  
===Stage 0x1B===
+
=== Stage 0x1B ===
 
boot0 reads a flag from SEEPROM to determine how long it should wait before attempting to initialize the SD card host.
 
boot0 reads a flag from SEEPROM to determine how long it should wait before attempting to initialize the SD card host.
 
  // Send debug mark
 
  // Send debug mark
Line 506: Line 498:
 
   
 
   
 
  // Delay execution arbitrarily
 
  // Delay execution arbitrarily
  if ((seeprom_1C_00 & 0x7C00) != 0)
+
  if ((seeprom_1C_00 & 0x7C00) != 0) {
{
+
     u32 time_now = *(u32 *)HW_TIMER;
     u32 time_now = *(u32 *)LT_TIMER;
 
 
      
 
      
 
     // Delay in multiples of 10000 ms
 
     // Delay in multiples of 10000 ms
Line 516: Line 507:
 
     u32 delay = sub_D412060(seeprom_delay);
 
     u32 delay = sub_D412060(seeprom_delay);
 
   
 
   
     while (time_now < delay)
+
     while (time_now < delay) {
    {
+
       time_now = *(u32 *)HW_TIMER;
       time_now = *(u32 *)LT_TIMER;
 
 
       delay = sub_D412060(seeprom_delay);
 
       delay = sub_D412060(seeprom_delay);
 
     }
 
     }
 
  }
 
  }
  
===Stage 0x1C===
+
=== Stage 0x1C ===
 
boot0 initializes EXI.
 
boot0 initializes EXI.
 
  // Send debug mark
 
  // Send debug mark
Line 529: Line 519:
 
   
 
   
 
  // Assert RSTB_IOEXI
 
  // Assert RSTB_IOEXI
  u32 resets = *(u32 *)LT_RESETS;
+
  u32 resets = *(u32 *)HW_RSTB;
  *(u32 *)LT_RESETS = resets | 0x10000;
+
  *(u32 *)HW_RSTB = resets | 0x10000;
 
   
 
   
 
  // Setup EXI
 
  // Setup EXI
 
  sub_D410BB8();
 
  sub_D410BB8();
  
===Stage 0x1D===
+
=== Stage 0x1D ===
 
boot0 uses EXI to read events from the [[Hardware/RTC|RTC]]. If UNSTBL_PWR is set in RTC_CONTROL0, boot0 will attempt to load a recovery signed boot1 image from a SD card.
 
boot0 uses EXI to read events from the [[Hardware/RTC|RTC]]. If UNSTBL_PWR is set in RTC_CONTROL0, boot0 will attempt to load a recovery signed boot1 image from a SD card.
 
  // Send debug mark
 
  // Send debug mark
Line 544: Line 534:
 
 
 
 
 
  // We got a response from EXI0
 
  // We got a response from EXI0
  if (result)
+
  if (result) {
{
 
 
     u32 exi0_reply = *(u32 *)exi0_out_buf;
 
     u32 exi0_reply = *(u32 *)exi0_out_buf;
 
      
 
      
Line 557: Line 546:
 
     goto exit;
 
     goto exit;
  
===Stage 0x1E===
+
=== Stage 0x1E ===
 
boot0 starts by configuring the SDC0S0Power GPIO. It then initializes and configures the SD host controller, flushes AHB memory and loads the recovery image's ancast header from the SD card. It checks the recovery image's size by looking at the size field in it's header (must not exceed 0xF800, so it doesn't overflow boot1's memory region) and then reads in the full image into memory address 0x0D400000 (replacing what was read from the NAND).
 
boot0 starts by configuring the SDC0S0Power GPIO. It then initializes and configures the SD host controller, flushes AHB memory and loads the recovery image's ancast header from the SD card. It checks the recovery image's size by looking at the size field in it's header (must not exceed 0xF800, so it doesn't overflow boot1's memory region) and then reads in the full image into memory address 0x0D400000 (replacing what was read from the NAND).
 
This stage only executes if EXI told boot0 to load an image from the SD card.
 
This stage only executes if EXI told boot0 to load an image from the SD card.
Line 564: Line 553:
 
   
 
   
 
  // Disable SDC0S0Power interrupts
 
  // Disable SDC0S0Power interrupts
  u32 gpio_intmask_val = *(u32 *)LT_GPIO_INTMASK;
+
  u32 gpio_intmask_val = *(u32 *)HW_GPIO_INTMASK;
  *(u32 *)LT_GPIO_INTMASK = gpio_intmask_val & 0xBFFFFFFF;
+
  *(u32 *)HW_GPIO_INTMASK = gpio_intmask_val & 0xBFFFFFFF;
 
   
 
   
 
  // Set SDC0S0Power GPIO direction to output
 
  // Set SDC0S0Power GPIO direction to output
  u32 gpio_dir_val = *(u32 *)LT_GPIO_DIR;
+
  u32 gpio_dir_val = *(u32 *)HW_GPIO_DIR;
  *(u32 *)LT_GPIO_DIR = gpio_dir_val | 0x40000000;
+
  *(u32 *)HW_GPIO_DIR = gpio_dir_val | 0x40000000;
 
 
 
 
 
  // Enable SDC0S0Power GPIO
 
  // Enable SDC0S0Power GPIO
  u32 gpio_enable_val = *(u32 *)LT_GPIO_ENABLE;
+
  u32 gpio_enable_val = *(u32 *)HW_GPIO_ENABLE;
  *(u32 *)LT_GPIO_ENABLE = gpio_enable_val | 0x40000000;
+
  *(u32 *)HW_GPIO_ENABLE = gpio_enable_val | 0x40000000;
 
   
 
   
 
  // Clear SDC0S0Power GPIO output
 
  // Clear SDC0S0Power GPIO output
  u32 gpio_out_val = *(u32 *)LT_GPIO_OUT;
+
  u32 gpio_out_val = *(u32 *)HW_GPIO_OUT;
  *(u32 *)LT_GPIO_OUT = gpio_out_val & 0xBFFFFFFF;
+
  *(u32 *)HW_GPIO_OUT = gpio_out_val & 0xBFFFFFFF;
 
   
 
   
 
  // Delay execution arbitrarily again
 
  // Delay execution arbitrarily again
  u32 time_now = *(u32 *)LT_TIMER;
+
  u32 time_now = *(u32 *)HW_TIMER;
 
      
 
      
 
  // Delay in multiples of 20000 ms
 
  // Delay in multiples of 20000 ms
Line 588: Line 577:
 
  u32 delay = sub_D412060(seeprom_delay);
 
  u32 delay = sub_D412060(seeprom_delay);
 
   
 
   
  while (time_now < delay)
+
  while (time_now < delay) {
{
+
     time_now = *(u32 *)HW_TIMER;
     time_now = *(u32 *)LT_TIMER;
 
 
     delay = sub_D412060(seeprom_delay);
 
     delay = sub_D412060(seeprom_delay);
 
  }
 
  }
Line 666: Line 654:
 
     throw_error();
 
     throw_error();
  
===Stage 0x1F===
+
=== Stage 0x1F ===
 
boot0 checks if the recovery image is encrypted or not (it is not encrypted in factory mode).
 
boot0 checks if the recovery image is encrypted or not (it is not encrypted in factory mode).
 
This stage only executes if EXI told boot0 to load an image from the SD card.
 
This stage only executes if EXI told boot0 to load an image from the SD card.
Line 676: Line 664:
 
     goto skip_boot1_decrypt;
 
     goto skip_boot1_decrypt;
  
===Stage 0x20===
+
=== Stage 0x20 ===
 
boot0 verifies the recovery image's hash (SHA-1) and signature (RSA).
 
boot0 verifies the recovery image's hash (SHA-1) and signature (RSA).
 
This stage only executes if EXI told boot0 to load an image from the SD card.
 
This stage only executes if EXI told boot0 to load an image from the SD card.
Line 689: Line 677:
 
     throw_error();
 
     throw_error();
  
===Stage 0x21===
+
=== Stage 0x21 ===
 
boot0 decrypts the recovery image (using the AES engine) in place.
 
boot0 decrypts the recovery image (using the AES engine) in place.
 
This stage only executes if EXI told boot0 to load an image from the SD card.
 
This stage only executes if EXI told boot0 to load an image from the SD card.
Line 698: Line 686:
 
  AES_Decrypt(boot1_key, 0x0D400000, image_blocks);
 
  AES_Decrypt(boot1_key, 0x0D400000, image_blocks);
  
===Stages 0x22, 0x23 and 0x24===
+
=== Stages 0x22, 0x23 and 0x24 ===
 
These stages do not exist, but code leftovers indicate they could have been related to loading a recovery image via the 802.11 Wireless host.
 
These stages do not exist, but code leftovers indicate they could have been related to loading a recovery image via the 802.11 Wireless host.
  
===Stage 0x25===
+
=== Stage 0x25 ===
 
boot0 clears boot1 and SEEPROM keys from memory, calculates and stores how long it took to run and returns.
 
boot0 clears boot1 and SEEPROM keys from memory, calculates and stores how long it took to run and returns.
 
  // Send debug mark
 
  // Send debug mark
Line 720: Line 708:
 
  return;
 
  return;
  
==Loading boot1==
+
== Loading boot1 ==
 
After boot0's main function returns, execution falls into the pointer that was set in the LR register.
 
After boot0's main function returns, execution falls into the pointer that was set in the LR register.
 
  // Jump to boot1
 
  // Jump to boot1
Line 741: Line 729:
 
Since boot0 finishes by setting r0 to 0x0D400200, returning from boot0 is equivalent to call sub_D4100F8(0x0D400200).
 
Since boot0 finishes by setting r0 to 0x0D400200, returning from boot0 is equivalent to call sub_D4100F8(0x0D400200).
  
==Error codes==
+
== Error codes ==
 
In addition to sending debug markers during execution, boot0 also sends error codes through debug ports using GPIO.
 
In addition to sending debug markers during execution, boot0 also sends error codes through debug ports using GPIO.
 
  // Send the error code
 
  // Send the error code
Line 769: Line 757:
 
 
 
 
 
     // Output error code
 
     // Output error code
     u32 gpio_out_val = *(u32 *)LT_GPIO_OUT;
+
     u32 gpio_out_val = *(u32 *)HW_GPIO_OUT;
 
     SendGPIODebugOut((gpio_out_val << 0x08) >> 0x18);
 
     SendGPIODebugOut((gpio_out_val << 0x08) >> 0x18);
 
   
 
   

Revision as of 21:40, 19 November 2019

Analogous to the old Wii, the Wii U also has a first-stage bootloader dubbed boot0, which is placed inside 16K of Mask ROM in the Latte's ARM core Starbuck. Being 2x bigger than the Wii version, Wii U's boot0 contains a number of features that include the ability of loading a recovery second-stage image from a SD card.

What follows are general descriptions and pseudo-code that illustrates the several sub-stages of the Wii U's boot0.

Initialization

boot0 runs from address 0xFFFF0000 where the ARM exception vectors are located. At this point, all exception vectors point to themselves (deadlock) except for the reset vector. Following the execution into the reset vector, the following takes place:

// Clear 0x10 bytes (will be used later)
memset_range(0x0D417FE0, 0x0D417FF0, 0, 0x04);

// Copy boot0 into a mirror in SRAM
memcpy(0x0D4100A0, 0xFFFF00A0, 0x3680);

// Set LR to a deadlock
LR = 0xFFFF004C

// Set PC
PC = sub_D4100A0

// Deadlock
loc_D41004C();

In other words, boot0 copies it's own code (starts at 0xFFFF00A0) into a special memory region in SRAM (0x0D410000) and jumps there. This behavior could also be observed, to some extent, in the old Wii's bootloader.

Setup

Right after boot0 copies itself over to SRAM, it does the following:

// Invalidate ICache
MCR p15, 0, R0,c7,c5, 0

// Invalidate DCache
MCR p15, 0, R0,c7,c6, 0

// Read control register
MRC p15, 0, R0,c1,c0, 0

// Set replacement strategy to Round-Robin
r0 = r0 | 0x1000

// Write control register
MCR p15, 0, R0,c1,c0, 0

// Clear 0x351C bytes after boot0
memset_range(0x0D413720, 0x0D416C3C, 0, 0x04);

// Set stack pointer
SP = 0x0D414204 + 0x1000

// Set return address
LR = sub_D4100F8		// Jump to boot1

// Set PC
PC = sub_D410384		// boot0 main

Essentially, sets up it's own stack and jumps to boot0's main function.

Main

This is the bulk of the first-stage bootloader. During the main function's execution, boot0 will send different signals to debug ports via GPIO. These signals can be used to represent error codes or mark different execution sub-stages.

Start

boot0 reads and saves the value from register HW_TIMER.

// Get timer value
u32 time_now = *(u32 *)HW_TIMER;

// Set start time
*(u32 *)0x0D413768 = 0;

Stage 0x00

boot0 sets a flag in HW_SPARE1 and configures debug ports' GPIOs.

// Send debug mark
SendGPIODebugOut(0x00);

u32 spare1_val = *(u32 *)HW_SPARE1;

// Set something in HW_SPARE1
*(u32 *)HW_SPARE1 = spare1_val | 0xC0;

// Enable GPIO for debug ports
u32 gpio_enable_val = *(u32 *)HW_GPIO_ENABLE;
*(u32 *)HW_GPIO_ENABLE = gpio_enable_val | 0x00FF0000;

// Set direction to output
u32 gpio_dir_val = *(u32 *)HW_GPIO_DIR;
*(u32 *)HW_GPIO_DIR = gpio_dir_val | 0x00FF0000;

Stage 0x01

boot0 sets something for memory swap.

// Send debug mark
SendGPIODebugOut(0x01);

// Set memory swap
*(u32 *)HW_SRNPROT = 0x07;

Stage 0x02

boot0 initializes the AES engine.

// Send debug mark
SendGPIODebugOut(0x02);

*(u32 *)AES_CTRL = 0;
*(u32 *)AES_KEY = 0;
*(u32 *)AES_KEY = 0;
*(u32 *)AES_KEY = 0;
*(u32 *)AES_KEY = 0;
*(u32 *)AES_IV = 0;
*(u32 *)AES_IV = 0;
*(u32 *)AES_IV = 0;
*(u32 *)AES_IV = 0;
*(u32 *)AES_SRC = 0;
*(u32 *)AES_DEST = 0;

Stage 0x03

boot0 initializes the SHA-1 engine.

// Send debug mark
SendGPIODebugOut(0x03);

*(u32 *)SHA_CTRL = 0;
*(u32 *)SHA_H0 = 0;
*(u32 *)SHA_H1 = 0;
*(u32 *)SHA_H2 = 0;
*(u32 *)SHA_H3 = 0;
*(u32 *)SHA_H4 = 0;
*(u32 *)SHA_SRC = 0;

Stage 0x04

boot0 sets something in LT_COMPAT_AHB and enables OTP reading.

// Send debug mark
SendGPIODebugOut(0x04);

// Set something in AHB compat
u32 ahb_val = *(u32 *)LT_AHBCMPT;
*(u32 *)LT_AHBCMPT = (ahb_val & 0xFFFFF3FF) | 0xC00;

// Enable OTP reading
SetOTPReadCommand();

Stages 0x05, 0x06 and 0x07

boot0 asserts some resets and enables EXI. These three stages are all merged together and the only signal sent to the debug ports is effectively 0x05.

// Send debug mark
SendGPIODebugOut(0x05);

// Assert RSTB_IOPI
u32 resets = *(u32 *)HW_RSTB;
*(u32 *)HW_RSTB = resets | 0x80000;

// Assert RSTB_IOMEM
u32 resets = *(u32 *)HW_RSTB;
*(u32 *)HW_RSTB = resets | 0x40000;

// Enable EXI
u32 aip_prot = *(u32 *)HW_AIP_PROT;
*(u32 *)HW_AIP_PROT = aip_prot | 0x01;

Stage 0x08

During this stage boot0 reads all it needs from the OTP.

// Send debug mark
SendGPIODebugOut(0x08);

// Read security level flag from OTP
ReadOTP(0x20, 0x0D413760, 0x04);

// Read IOStrength flags from OTP
ReadOTP(0x21, 0x0D41375C, 0x04);

// Read SEEPROM pulse length from OTP
ReadOTP(0x22, 0x0D413758, 0x04);

// Read SEEPROM key from OTP
ReadOTP(0x28, 0x0D41376C, 0x10);

// Read boot1 key from OTP
ReadOTP(0xE8, 0x0D41377C, 0x10);

Stage 0x09

boot0 analyses the IOStrength flags read in the previous stage and sets the strength of various devices.

// Send debug mark
SendGPIODebugOut(0x09);

// Setup devices' strength
u32 iostrength_flags = *(u32 *)0x0D41375C;
u32 iostrength_ctrl0_val = *(u32 *)HW_IOSTRCTRL0;
u32 iostrength_ctrl1_val = *(u32 *)HW_IOSTRCTRL1;

if (((iostrength_flags >> 0x0F) & 0x01) != 0) {
    iostrength_ctrl0_val &= 0xFFFC7FFF;
    iostrength_ctrl0_val |= ((iostrength_flags << 0x11) >> 0x1D) << 0x0F;
}

*(u32 *)HW_IOSTRCTRL0 = iostrength_ctrl0_val;

if (((iostrength_flags >> 0x0F) & 0x01) != 0) {
    iostrength_ctrl1_val &= 0xFFFC7FFF;
    iostrength_ctrl1_val |= (iostrength_flags & 0x07) << 0x0F;
}

if (((iostrength_flags >> 0x07) & 0x01) != 0) {
    iostrength_ctrl1_val &= 0xFFE3FFFF;
    iostrength_ctrl1_val |= ((iostrength_flags << 0x19) >> 0x1D) << 0x12;
}

if (((iostrength_flags >> 0x0B) & 0x01) != 0) {
    iostrength_ctrl1_val &= 0xFFFF8FFF;
    iostrength_ctrl1_val |= ((iostrength_flags << 0x15) >> 0x1D) << 0x0C;
}

if (((iostrength_flags >> 0x13) & 0x01) != 0) {
    iostrength_ctrl1_val &= 0xFFFF8FFF;
    iostrength_ctrl1_val |= ((iostrength_flags << 0x0D) >> 0x1D) << 0x15;
}

*(u32 *)HW_IOSTRCTRL1 = iostrength_ctrl1_val;

Stage 0x0A

boot0 sets the SEEPROM's pulse length and configures the SEEPROM GPIOs.

// Send debug mark
SendGPIODebugOut(0x0A);

u32 seeprom_pulse = *(u32 *)0x0D413758;

// Set default value (retail)
if (seeprom_pulse == 0)
   seeprom_pulse = 0xFA;

// Store pulse value at 0x0D413460
sub_D411774();

// Configure SEEPROM GPIOs
Set_SEEPROM_GPIO();

Stage 0x0B

boot0 analyses the OTP security level flag and decides which keys to use.

// Send debug mark
SendGPIODebugOut(0x0B);

u32 sec_lvl_flag = *(u32 *)0x0D413760;

// Forcefully throw error
if (sec_lvl_flag == 0x40000000)
   throw_error();

// Factory mode
if (sec_lvl_flag == 0x00000000)
    set_empty_aes_keys();
else {
    // Disable boot1 AES key access
    u32 otpprot_val = *(u32 *)LT_OTPPROT;
    *(u32 *)LT_OTPPROT = otpprot_val & 0xDFFFFFFF;
    
    // This is a devkit unit
    if ((sec_lvl_flag & 0x18000000) == 0x08000000)
        set_debug_aes_keys();
    else if ((sec_lvl_flag & 0x10000000) == 0x10000000)  // This is a retail unit
        set_retail_aes_keys();
    else
        throw_error();
}

Stage 0x0C

boot0 generates a CRC32 table in it's stack.

// Send debug mark
SendGPIODebugOut(0x0C);
	
// Generate CRC32 table from
// 0x0D413D44 to 0x0D414140
CRC32_Gen();

Stage 0x0D

boot0 uses the SEEPROM key to decrypt SEEPROM data related to the boot process.

// Send debug mark
SendGPIODebugOut(0x0D);

// Set SEEPROM AES key
AES_Set_Key(aes_seeprom_key);
	
// Read and decrypt from SEEPROM
SEEPROM_Read(0x1C, 0x0D41378C, 0x01);

// Read and decrypt from SEEPROM
SEEPROM_Read(0x1D, 0x0D41379C, 0x01);

// Read and decrypt from SEEPROM
SEEPROM_Read(0x1E, 0x0D4137AC, 0x01);

Stage 0x0E

boot0 validates the data decrypted from the SEEPROM at offset 0x1C0 in the previous stage using CRC32. This data is used to set miscellaneous settings during boot0's execution.

// Send debug mark
SendGPIODebugOut(0x0E);

// Factory mode uses an empty SEEPROM key
if (!aes_seeprom_key)
{
   // Match CRC32 with the table
   // CRC32 is at seeprom_1C + 0x0C
   bool match = sub_D410168(seeprom_1C, 0x10);

   // CRC32 didn't match, but we are in factory
   // mode, so we just set everything to 0
   if (!match)
      memset(seeprom_1C, 0, 0x10);
}
else   // Normal mode has a valid SEEPROM key
{
   // Match CRC32 with the table
   // CRC32 is at seeprom_1C + 0x0C
   bool match = sub_D410168(seeprom_1C, 0x10);

   // CRC32 didn't match, throw an error
   if (!match)
      throw_error();
}

// Read SEEPROM boot flags
u16 seeprom_1C_00 = *(u16 *)0x0D41378C;
u16 seeprom_1C_02 = *(u16 *)0x0D41378C + 0x02;
u32 seeprom_1C_04 = *(u32 *)0x0D41378C + 0x04;
u32 seeprom_1C_08 = *(u32 *)0x0D41378C + 0x08;

// Factory mode doesn't need boot1's data
if (!aes_seeprom_key) {
   boot1_version = 0;
   boot1_sector = 0;
}

Stage 0x0F

boot0 validates the data decrypted from the SEEPROM at offsets 0x1D0 and 0x1E0 in the previous stage using CRC32. This data is used to determine boot1's version and sector inside the NAND. This stage is skipped in factory mode.

// Send debug mark
SendGPIODebugOut(0x0F);

// Match CRC32 with the table
// CRC32 is at seeprom_1D + 0x0C
bool match1 = sub_D410168(seeprom_1D, 0x10);

// Match CRC32 with the table
// CRC32 is at seeprom_1E + 0x0C
bool match2 = sub_D410168(seeprom_1E, 0x10);

// Both sections have invalid CRC32
if (!match1 && !match2)
   throw_error();

// The SEEPROM specifies the versions and sectors
// of the two boot1 images stored in the NAND
u16 boot1_version1 = (u16)seeprom_1D;
u16 boot1_version2 = (u16)seeprom_1E;
u16 boot1_sector1 = (u16)seeprom_1D + 0x02;
u16 boot1_sector2 = (u16)seeprom_1E + 0x02;

// The second version is always the most recent
u16 boot1_version = (boot1_version2 > boot1_version1) ? boot1_version2 : boot1_version1;
u16 boot1_sector = (boot1_version2 > boot1_version1) ? boot1_sector2 : boot1_sector1;

// Check if seeprom_1C_00 is not 0x400
if ((seeprom_1C_00 & 0x3FF) != 0) {
   // Store at 0x0D414200
   sub_D41203C(seeprom_1C_00 & 0x3FF);
}

Stages 0x10, 0x11 and 0x12

boot0 configures the Starbuck's clock multiplier. These stages are optional and only execute if the 2-byte flag read from the SEEPROM at offset 0x1C0 translates to a negative value. The three stages are merged together and the only signal sent to the debug ports is effectively 0x10.

// Send debug mark
SendGPIODebugOut(0x10);

// Allow IRQ 12 (LT)
*(u32 *)LT_ARMIRQMASKLT = 0x1000;
	
// Turn IOP2X on?
*(u32 *)LT_IOP2X = 0x03;
	
// Wait for interrupt
sub_D4132E8();
	
// Disable IRQ 12 (LT)
*(u32 *)LT_ARMIRQMASKLT = 0;

Stage 0x13

boot0 analyses the remaining data read from SEEPROM at offset 0x1C0 and uses it to configure the NAND_CONFIG and NAND_BANK registers. It then initializes the NAND engine.

// Send debug mark
SendGPIODebugOut(0x13);

// Use supplied NAND configuration
if (((seeprom_1C_02 >> 0x0E) & 0x01) != 0)
   *(u32 *)NAND_CONFIG = (u32)seeprom_1C_04;

// Use supplied NAND bank
if (((seeprom_1C_02 >> 0x0D) & 0x01) != 0)
   *(u32 *)NAND_BANK = (u32)seeprom_1C_08;

// Determine if boot1 is in SLC or SLCCMPT
bool boot1_isSLC = (boot1_sector >> 0x0C) ? true : false;

// Init NAND
u32 result = sub_D411564(boot1_isSLC);

Stage 0x14

boot0 checks if it's start time is valid (must be 0, otherwise boot0 throws an error) and saves the current time (in order to track how long boot1's reading took). This stage only executes if NAND engine's initialization was successful.

// Send debug mark
SendGPIODebugOut(0x14);
 
// Bad start time
if (time_at_boot != 0)
   throw_error();

// Take note of the current time
u32 boot1_read_time = *(u32 *)LT_TIMER;

Stages 0x15, 0x16 and 0x17

boot0 flushes AHB memory, reads boot1's ancast header from NAND and checks the boot1's image size by looking into the respective field inside the header. If the size is valid (must not exceed 0xF800, so it doesn't overflow boot1's memory region), boot0 then proceeds to read the full boot1's image from NAND into address 0x0D400000: Finally, boot0 calculates how long this operation took and stores this value for the IOS-MCP to read later on. These stages only execute if NAND engine's initialization was successful and only signal 0x15 is sent to the debug ports.

// Send debug mark
SendGPIODebugOut(0x15);

// AHB memory flush
ahbMemFlush(0);
	
// Bit 15 in seeprom_1C_02 tells to ignore NAND errors
bool ignore_errors = ((seeprom_1C_02 >> 0x0F) & 0x01) ? true : false;

// Read boot1's header from NAND into 0x0D400000
u32 result = NAND_Read(0x0D400000, boot1_sector << 0x06, 0x01, ignore_errors);

// Failed to read from NAND
if (!result)
   throw_error();
	
// Copy the last 0x60 bytes of
// boot1's ancast image's header
memcpy(0x0D413940, 0x0D4001A0, 0x60);
		
// Check ancast image's body size
u32 ancast_body_size = *(u32 *)0x0D41394C;

// Calculate boot1's actual size
u32 boot1_image_size = ((ancast_body_size + 0x9FF) & 0xFFFFF800) - 0x01;

// The image is too big
if (boot1_image_size > 0xF7FF)
   throw_error();

// AHB memory flush
ahbMemFlush(0);

// Read the full boot1 image
result = NAND_Read(0x0D400000, boot1_sector << 0x06, boot1_image_size >> 0x0B, ignore_errors);
 			
// Failed to read from NAND
if (!result)
   throw_error();

// Calculate how long it took to
// read boot1 (MCP later reads this)
u32 time_now = *(u32 *)HW_TIMER;
*(u32 *)0x0D417FE4 = time_now - boot1_read_time;

Stage 0x18

boot0 checks if boot1 is encrypted or not (boot1 is not encrypted in factory mode). This stage only executes if NAND engine's initialization was successful.

// Send debug mark
SendGPIODebugOut(0x18);

// No need to decrypt in factory mode
if (!aes_boot1_key)
   goto skip_boot1_decrypt;

Stage 0x19

boot0 verifies boot1's hash (SHA-1) and signature (RSA). This stage only executes if NAND engine's initialization was successful and if factory mode is not enabled.

// Send debug mark
SendGPIODebugOut(0x19);

// Hash and verify boot1's image
u32 image_blocks = Calc_SHA1_RSA(0, 0x0D400000);

// Failed to verify
if (image_blocks == 0)
   throw_error();

Stage 0x1A

boot0 decrypts boot1 (using the AES engine) in place. This stage only executes if NAND engine's initialization was successful and factory mode is not enabled.

// Send debug mark
SendGPIODebugOut(0x1A);

// Decrypt boot1
AES_Decrypt(boot1_key, 0x0D400000, image_blocks);		

Stage 0x1B

boot0 reads a flag from SEEPROM to determine how long it should wait before attempting to initialize the SD card host.

// Send debug mark
SendGPIODebugOut(0x1B);

// Delay execution arbitrarily
if ((seeprom_1C_00 & 0x7C00) != 0) {
   u32 time_now = *(u32 *)HW_TIMER;
   
   // Delay in multiples of 10000 ms
   u32 seeprom_delay = (u32)(seeprom_1C_00 >> 0x0A) * 0x2710;

   // Calculate delay
   u32 delay = sub_D412060(seeprom_delay);

   while (time_now < delay) {
      time_now = *(u32 *)HW_TIMER;
      delay = sub_D412060(seeprom_delay);
   }
}

Stage 0x1C

boot0 initializes EXI.

// Send debug mark
SendGPIODebugOut(0x1C);

// Assert RSTB_IOEXI
u32 resets = *(u32 *)HW_RSTB;
*(u32 *)HW_RSTB = resets | 0x10000;

// Setup EXI
sub_D410BB8();

Stage 0x1D

boot0 uses EXI to read events from the RTC. If UNSTBL_PWR is set in RTC_CONTROL0, boot0 will attempt to load a recovery signed boot1 image from a SD card.

// Send debug mark
SendGPIODebugOut(0x1D);

// Request from EXI0
u32 result = sub_D410D78(exi0_out_buf);
				
// We got a response from EXI0
if (result) {
   u32 exi0_reply = *(u32 *)exi0_out_buf;
   
   // UNSTBL_PWR bit is set in RTC_CONTROL0
   if ((exi0_reply << 0x14) < 0)
      load_sd = true;
}

// Nothing to do, skip the next stages
if (!load_sd)
   goto exit;

Stage 0x1E

boot0 starts by configuring the SDC0S0Power GPIO. It then initializes and configures the SD host controller, flushes AHB memory and loads the recovery image's ancast header from the SD card. It checks the recovery image's size by looking at the size field in it's header (must not exceed 0xF800, so it doesn't overflow boot1's memory region) and then reads in the full image into memory address 0x0D400000 (replacing what was read from the NAND). This stage only executes if EXI told boot0 to load an image from the SD card.

// Send debug mark
SendGPIODebugOut(0x1E);

// Disable SDC0S0Power interrupts
u32 gpio_intmask_val = *(u32 *)HW_GPIO_INTMASK;
*(u32 *)HW_GPIO_INTMASK = gpio_intmask_val & 0xBFFFFFFF;

// Set SDC0S0Power GPIO direction to output
u32 gpio_dir_val = *(u32 *)HW_GPIO_DIR;
*(u32 *)HW_GPIO_DIR = gpio_dir_val | 0x40000000;
				
// Enable SDC0S0Power GPIO
u32 gpio_enable_val = *(u32 *)HW_GPIO_ENABLE;
*(u32 *)HW_GPIO_ENABLE = gpio_enable_val | 0x40000000;

// Clear SDC0S0Power GPIO output
u32 gpio_out_val = *(u32 *)HW_GPIO_OUT;
*(u32 *)HW_GPIO_OUT = gpio_out_val & 0xBFFFFFFF;

// Delay execution arbitrarily again
u32 time_now = *(u32 *)HW_TIMER;
   
// Delay in multiples of 20000 ms
u32 seeprom_delay = (u32)((seeprom_1C_02 << 0x16) >> 0x1E) * 0x4E20;

// Calculate delay
u32 delay = sub_D412060(seeprom_delay);

while (time_now < delay) {
   time_now = *(u32 *)HW_TIMER;
   delay = sub_D412060(seeprom_delay);
}

// Initialize host controller
u32 host_id = 0;
u32 reg_offset = 0;
u32 result = sub_D411290(host_id, reg_offset, sd_handle_buf);

// Failed to initialize the controller
if (!result)
   throw_error();

// Grab the handle returned from initialization
u32 sd_handle = *(u32 *)sd_handle_buf;

// It's possible to specify the clock and switch_func
// values for the SD card from flags in the SEEPROM
u32 sd_clk = 0;
u32 sd_switch_func = 0;

u8 seeprom_sd_flag1 = (u8)(seeprom_1C_02 << 0x14);
u8 seeprom_sd_flag2 = (u8)((seeprom_1C_02 << 0x15) >> 0x1F);

// Set the SD card's clock value
if (seeprom_sd_flag1 > 0)
   sd_clk = 0x01;
else
   sd_clk = seeprom_sd_flag1 & 0xFF;

// Set the SD card's switch_func value
// This is passed to SD card CMD6 and can be
// used to turn high speed on
sd_switch_func = seeprom_sd_flag2;

// Setup the SD card
result = sub_D41139C(host_id, reg_offset, sd_handle, sd_clk, sd_switch_func);

// Failed to setup the SD card
if (!result)
   throw_error();

// AHB memory flush
ahbMemFlush(0);

// Read SD recovery image's ancast header
result = sub_D411544(host_id, reg_offset, 0x0D400000, 0x400);

// Failed to read the recovery image's ancast header
if (!result)
   throw_error();

// Copy the last 0x60 bytes of
// boot1's ancast image's header
memcpy(0x0D413940, 0x0D4001A0, 0x60);
		
// Check ancast image's body size
u32 ancast_body_size = *(u32 *)0x0D41394C;

// Calculate boot1's actual size
u32 boot1_image_size = ((ancast_body_size + 0x3FF) & 0xFFFFF800) - 0x01;

// The image is too big
if (boot1_image_size > 0xF7FF)
   throw_error();

// AHB memory flush
ahbMemFlush(0);

// Read the SD recovery image
result = sub_D411544(host_id, reg_offset, 0x0D400000, boot1_image_size);

// Failed to read the recovery image
if (!result)
   throw_error();

Stage 0x1F

boot0 checks if the recovery image is encrypted or not (it is not encrypted in factory mode). This stage only executes if EXI told boot0 to load an image from the SD card.

// Send debug mark
SendGPIODebugOut(0x1F);

// No need to decrypt in factory mode
if (!aes_boot1_key)
   goto skip_boot1_decrypt;

Stage 0x20

boot0 verifies the recovery image's hash (SHA-1) and signature (RSA). This stage only executes if EXI told boot0 to load an image from the SD card.

// Send debug mark
SendGPIODebugOut(0x20);

// Hash and verify boot1's image
u32 image_blocks = Calc_SHA1_RSA(0, 0x0D400000);

// Failed to verify
if (image_blocks == 0)
   throw_error();

Stage 0x21

boot0 decrypts the recovery image (using the AES engine) in place. This stage only executes if EXI told boot0 to load an image from the SD card.

// Send debug mark
SendGPIODebugOut(0x21);

// Decrypt boot1
AES_Decrypt(boot1_key, 0x0D400000, image_blocks);

Stages 0x22, 0x23 and 0x24

These stages do not exist, but code leftovers indicate they could have been related to loading a recovery image via the 802.11 Wireless host.

Stage 0x25

boot0 clears boot1 and SEEPROM keys from memory, calculates and stores how long it took to run and returns.

// Send debug mark
SendGPIODebugOut(0x25);

// Clear boot1 key
memset(0x0D41377C, 0, 0x10);

// Clear SEEPROM key
memset(0x0D41376C, 0, 0x10);

// boot1/recovery image start address
r0 = 0x0D400200

// Store boot0's boot time
*(u32 *)0x0D417FE0 = time_now - initial_time;

return;

Loading boot1

After boot0's main function returns, execution falls into the pointer that was set in the LR register.

// Jump to boot1
sub_D4100F8(addr)
{
   r1 = addr

   // Read control register
   MRC p15, 0, R0,c1,c0, 0

   // Set replacement strategy to normal
   r0 = r0 & ~(0x1000)

   // Write control register
   MCR p15, 0, R0,c1,c0, 0

   PC = addr
}

Since boot0 finishes by setting r0 to 0x0D400200, returning from boot0 is equivalent to call sub_D4100F8(0x0D400200).

Error codes

In addition to sending debug markers during execution, boot0 also sends error codes through debug ports using GPIO.

// Send the error code
SendGPIODebugOut(error_code);
		
// Clear boot1 key
memset(0x0D41377C, 0, 0x10);
					
// Clear SEEPROM key
memset(0x0D41376C, 0, 0x10);

// Lock execution and output error code
while (1)
{
   // Output 0x02
   SendGPIODebugOut(0x02);
		
   // Wait
   UDelay(0x7A120);

   // Output time
   u32 time = *(u32 *)0x0D413768;
   SendGPIODebugOut(time);
						
   // Wait
   UDelay(0x7A120);
	
   // Output error code					
   u32 gpio_out_val = *(u32 *)HW_GPIO_OUT;
   SendGPIODebugOut((gpio_out_val << 0x08) >> 0x18);

   // Wait						
   UDelay(0x7A120);
 }
Error code Notes
0xC1 OTP security level flag is 0x40000000
0xC2 OTP security level flag is invalid
0xC3 SEEPROM CRC32 mismatch from data at offset 0x1C0
0xC4 SEEPROM CRC32 mismatch from data at offset 0x1D0 and 0x1E0
0xD1 Failed to read boot1's ancast header from NAND
0xD2 Failed to start SD host controller
0xD3 Failed to setup SD card
0xD4 Failed to read boot1's ancast header from SD card
0xD5 Bad start time during NAND initialization
0xD6 Ancast image size overflow (from NAND)
0xD7 Failed to read boot1 image from NAND
0xD8 Ancast image size overflow (from SD card)
0xD7 Failed to read boot1 image from SD card
0xDA Failed to initialize NAND engine