Editing Iplloader

Jump to navigation Jump to search
Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.

The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.

Latest revision Your text
Line 1: Line 1:
<b>iplloader</b> <i>(also known as PRE-IPL or BootROM)</i> — is the first code to run on PSP main [[CPU]].
The iplloader, called "Lib-PSP iplloader" internally by Sony, also sometimes called PRE-IPL or BootROM, is the first code to run in PSP MIPS32 main CPU. Its role is to load the [[Initial Program Loader]]. iplloader contains the routines to boot into service mode and loads and decrypts the encrypted IPL from the NAND or Memory Stick.
 
Its role is to load and decrypt encrypted [[Initial Program Loader]] from the [[NAND_Flash_Memory|NAND]] or [[Memory Stick]]. iplloader contains the routines to boot into service mode.


= Location =
= Location =
Line 85: Line 83:
| style="background:#C3F500" | PSP (Retail)
| style="background:#C3F500" | PSP (Retail)
| style="background:#C3F500" | Tachyon 0x00600000-0x00900000
| style="background:#C3F500" | Tachyon 0x00600000-0x00900000
| style="background:#C3F500" | 10-09-2007 (build date in ROM)
| style="background:#C3F500" | 10-09-2007 (build date in the payload)
| style="background:#C3F500" | 0xCE8
| style="background:#C3F500" | 0xCE8
| style="background:#C3F500" | SHA-256: E511D3DC78A209610F5B3EFEA2BC64BF86B9DF14A9C279C4499FECBFD70E6BF9 (ROM)
| style="background:#C3F500" | SHA-256: E511D3DC78A209610F5B3EFEA2BC64BF86B9DF14A9C279C4499FECBFD70E6BF9 (ROM)
|-
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 0.920.000 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 0.931.010-0.995.000 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 06-22-2010 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 11-17-2010 (last modified date for 0.940I compat_sm.self)
| style="background:#FF8B00" | 0x60
| style="background:#FF8B00" | SHA-256: 98C8336C136DF901FC4EA38EB371AAF6F5402AC06574F107D3E2029BFC85CCAD (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 0.930.010-0.995.000 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 08-17-2010 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0x2C0
| style="background:#FF8B00" | 0x2C0
| style="background:#FF8B00" | SHA-256: 6D75EC720739C53228B1CA1AFF6CE073AE542BBB38FCC9B8710EC5EB3889B942 (full binary)
| style="background:#FF8B00" | SHA-256: 6D75EC720739C53228B1CA1AFF6CE073AE542BBB38FCC9B8710EC5EB3889B942 (full binary)
Line 194: Line 186:
== Memory mapping ==
== Memory mapping ==


The PSP iplloader is mapped to <b>0xBFC00000</b>, which is the MIPS reset vector, <i>i.e CPU initial program counter</i>.
The PSP iplloader is mapped to 0xBFC00000 which is the reset vector of PSP's MIPS R4000 CPU.


0.7.0 iplloader and onward are composed of two parts:
0.7.0 iplloader and onward are composed of two parts: a loader from 0xBFC00000 to 0xBFC0027F and a payload from 0xBFC00280 and ending at the size specified at 0xBFC000034 (little endian 0x2 bytes).
* a loader from <b>0xBFC00000</b> to <b>0xBFC0027F</b>
* a payload from <b>0xBFC00280</b> and ending at the size specified at <b>0xBFC000034</b> (<i>2 bytes with little endian order</i>).


The PSP iplloader distributed in PS Vita System Software version 0.996 and onward has its payload starting at 0xBFC00180, rather than at 0xBFC00280 on previous PS Vita software and PSP hardware versions.
The PSP iplloader distributed in PS Vita System Software version 0.996 and onward has its payload starting at 0xBFC00180, rather than at 0xBFC00280 on previous PS Vita software and PSP hardware versions.
Line 315: Line 305:
== Retail behaviour ==  
== Retail behaviour ==  


The full reverse engineered assembly code can be found in uOFW: https://github.com/uofw/uofw/tree/master/src/preipl.
=== 01g/02g ===
 
pseucode from Tachyon 0x00140000-0x00300000 iplloader payload:  
<pre>
int iplBlockNumber = 0;
u32 checksum = 0;
 
// load/decrypt all encrypted ipl blocks
while(1)
{
// copy an encrypted ipl block to 0xBFD00000-0xBFD01000 (4KB embedded cpu ram)
if (LoadIplBlock(iplBlockNum ber, block) < 0)
while(1);
 
// decrypt the ipl block in place (uh oh...)
if (DecryptIplBlock(block, block))
while(1);
 
// first block has zero as checksum because there is no previous block (another uh oh...)
if (block->checksum != checksum)
while(1);
 
// load the 'data' section of the ipl block to the specified address (0x040Fxxxx range)
if (block->loadaddr)
checksum = memcpy(block->loadaddr, block->data, block->blocksize);


=== The loader ===
// reached the end of the ipl, jump to the entry address (0x040F0000)
if (block->entry)
{
// clear caches
Dcache();
Icache();


The loader part is almost the same for all retail iplloader versions.
// jump to ipl - do not return
block->entry();
}


<pre>
iplBlockNumber++;
ctc0($4, $v0); // save $v0
if (*0xBC100000 != 0) { // NMI interrupts enabled
    if (cfc0($9) != 0) { // NMI handler
        jump to $9;
    } else {
        $v0 = cfc0($4); // restore $v0
        // disable the BEV bit (on R4400, it moves the non-reset and NMI interruption handling to 0x80000000 instead of 0xBFC00200, but it may have a different behavior here)
        mtc0(Status, mfc0(Status) & 0xFFBFFFFF);
        jump to mfc0(EBase);
    }
} else {
    ctc0($9, 0); // reset NMI handler
    sceKernelL1IcacheInvalidateAll();
    sceKernelL1DcacheInvalidateAll();
    memcpy(0xA0010000, preipl_payload, preipl_payload_size);
    $sp = 0x80013FF0;
    sync();
    jump to 0x80010000;
}
}
// image ends with a copyright string, and a build timestamp: 0x20040420, 0x20050104 and 0x20070910 for successive iplloader versions
</pre>
</pre>


=== First version ===
Tachyon revisions 0x00140000 to 0x00300000 iplloader pseudo code:
 
<pre>
PSP Disassembler Ver.0.20 Copyright(c)2005,2006 BOOSTER
incl. elf-lib 0.1r2 copyright (c) 2005 djhuevo
 
:file name '0x80010000.bin',size = 4096
Load 3684 NID's name
 
:Disasm
 
;copied by iplloader bootcode, from 0xbfc00280-0xbfc00d78
;here code is 0x80010000 to 0x80010af8
 
;
;$bfd00000-$bfd00fff : sector read buffer
;
 
;-------------------------------------------------------
;recovery mode selector
 
;be240004 GPIO READ REG.
; bit4 : device select ,0=NAND Flash,  1= rec-dev
;
;-------------------------------------------------------
 
;-------------------------------------------------------
;recovery boot device (rec-dev) HW assign
;
;bd200030 command register
00009007 : read request (write size = 8)
00002200 : read sector buffer (read size=200)
00008004 : ? status (write size = 8 , read size=8)
00004000 : ? status (read size=8)
00007001 : ? (data size=8?)
 
;bd200034 data register (read / write)
;bd200038 status register
; bit14:read data ready
; bit13:parameter wirte ready
; bit12:transmit finish ?
; bit 9:???? error
; bit 8:read data error
;
;bd20003c ???
bit15:reset device ?
;
;-------------------------------------------------------
 
 
;---------------------------------------------------------------------------
;entry
;---------------------------------------------------------------------------
L80010000:
;
;reset I/O
$800106B0(L80010a80)
;
  lui    r8,$bc10                          ;80010010[3C08BC10,'...<']
;
if(r8[$68]>>16 == 0) $80010034
;
r9 = r8[$78]
  ori    r9,r9,$0800                        ;80010028[35290800,'..)5']
  b      $80010040                          ;8001002C[10000004,'....']
  sw    r9,$78(r8)                        ;80010030[AD090078,'x...']
;
;80010034
  lw    r9,$7c(r8)                        ;80010034[8D09007C,'|...']
  ori    r9,r9,$0010                        ;80010038[35290010,'..)5']
  sw    r9,$7c(r8)                        ;8001003C[AD09007C,'|...']
  addiu  r4,0,$a                            ;80010040[2404000A,'...$']
  jal    $80010768                          ;80010044[0C0041DA,'.A..']
  sync                                      ;80010048[0000000F,'....']
;80010768
;
;check recovery boot mode switch
;
r8 = [$be240004] & 0x10 // GPIO bit 4
;
r9 = $80010194 // NAND read BLOCK entry
r10= $80010130 // NAND initialize(read FAT) entry
;
if(r8!=0)// $80010080
{
r9 = $80010248 // rec-dev read BLOCK entry
r10= $80010240 // rec-dev initialize entry
}
;80010080
[$80010808] = r9
[$8001080c] = 0x000000000
;
;call read FAT
;
(r10)()
;
r23 = 0  ; check sum of last block
;
;READ BLOCK LOOP
;
L80010098
r25 = [$80010808] ; read BLOCK entry
r4  = [$8001080c] ; block num of read
;
;call read body function
;
r2 = (r25)(r4,$bfd000000);
if(r2<0) $80010128
;
;decrypt 1000H block
;
r2 = $80010620($bfd00000,$bfd00000)
if(r2<0) $80010128
;
;+000c : check sum of last block ?
;
if( [$bfd0000c] != r23) $80010128
;
;+0000 : distination address
;+0004 : block size
;
r4 = [$bfd00000] ; +0000 : top pointer
r6 = [$bfd00004] ; +0004 : size
if(r4!=0)
{
;transmit BLOCK body
r23 = $80010688(r4,r16 + $10 , r6)
}
;
;+0008 : entry point or continue MARK check
;
r25 = r16[8]
if(r25==0) //$80010114
{
;L80010114:
[$8001080c]++  ; next block
goto $80010098
}
;
;cache ?
$800102D8()
;
;cache ?
$800102A0()
;
;goto IPL entry point
;
  jalr  r25                                ;8001010C[0320F809,'.. .']
;
;80010128
while(1); // HALT
;
;---------------------------------------------------------------------------
;read IPL FAT
;---------------------------------------------------------------------------
L80010130:
r16 =r31
;
;80010134
;NAND reset
$80010308()
;
;8001013C
r17  = $80 // top of IPL-FAT sector
;80010140
;read IPL FAT
r2 = $80010334(r17,8001081c,80010810)
if(r2<0) $8001018c
;80010164
r8 = r6[0]
r9 = r6[4]
r10= r6[8]
;ECC signature
if(r9 != $6dc64a38) // $8001018c
{
r17 += $20 // next IPL sector
goto $80010140
}
; jr    r16                                ;80010184[02000008,'....']
return


This version of the iplloader is used for Tachyon 0x00140000 to 0x00300000 (ie all versions of 01g except the few last).
;------------------------------------------------------------------------
;NAND read body
;
;r4 : fat logical ptr (400H bytes lba? )
;
;------------------------------------------------------------------------
L80010194:
[$80010800] = r31
[$80010804] = r4
;
r17 = r5
;get FAT location
r8 = $8001081c ; FAT table
r9 = (r4>>2)<<1
r8 += r9
r9 = (u16)r8[0]
;
r8 = (r9<<2) | (r4 & 3)
;
r16 = r8 << 3 ; * 8
r18 = 0
;800101CC
do
{
;read body one
r2 = $80010334(r16+r18,r17 + (r18<<9),80010810)
if(r2<0) $80010230
r8 = r6[0]
r9 = r6[4]
r10= r6[8]
;ECC signature
if(r9 != $6dc64a38) $80010230
r18++
}while(r18<8);
;80010220
r31 = [$80010800]
return r0


Here is the pseudocode of the payload (not including implementation details, and excluding the information to interface with NAND & MemoryStick):
L80010230:
<pre>
r31 = [$80010800]
0xBC100058 |= 0x00800000; // Enable GPIO clock
return -1
0xBC100050 |= 0x0000608E; // Enable bus clock for AW (which is GE) (RegA, RegB, Edram), KIRK, NAND (EMCSM) and APB (for syscon?)
 
0xBC10004C &= ~0x408;    // Clear reset for AW and KIRK
;------------------------------------------------------------------------
0xBC100078 |= 2;         // IO enable NAND (EMCSM)
;rec-dev initialize
0xBE240000 &= ~0x10;     // Disable GPIO pin 4 output
;------------------------------------------------------------------------
0xBE240040 |= 0x10;       // Enable GPIO pin 4 input
L80010240:
sleep(1);                // Wait a bit
return $800103B4()
0xBD500010 = 1;          // Initialize GE Edram
while (*0xBD500010 & 1 != 0) {} // Wait for the Edram to be initialized
0xBD500040 = 1;          // Finish initializing GE Edram


if (*0xBC100068 >> 16 != 0) { // Unknown bits
;------------------------------------------------------------------------
    *0xBC100078 |= 0x800; // Enable audio clock out??
;rec-dev read 1000H block
} else {
;------------------------------------------------------------------------
    *0xBC10007C |= 0x10; // Enable GPIO pin 4 (used for jigkick)
L80010248:
}
[$80010$800] = r31
sleep(10); // Wait a bit
;
int (*InitStorage)(void);
r16 = r4
int (*ReadBlock)(int blkIndex, void *destination);
r17 = r5
if (*0xBE240004 & 0x10 == 0) { // Check GPIO pin 4 to decide if we boot from NAND or MemoryStick (ie jigkick)
r18 = 0
    InitStorage = InitNand;
;8001025C
    ReadBlock = ReadNandBlock;
do{
} else {
r2 = $80010418(r18+0x10+r16<<3 ,r17 + r18<<9)
    InitStorage = InitMemoryStick;
if(r2<0) $8001025c
    ReadBlock = ReadMemoryStickBlock;
r18++
}
}while(r18<8);
;
r31 = [$80010$800]
return r2


u32 iplBlockIdx = 0;
;------------------------------------------------------------------------
u32 lastBlockChecksum = 0;
;cache ?
;------------------------------------------------------------------------
L800102A0:
  mfc0  r8,Config                          ;800102A0[40088000,'...@']
  addiu  r9,0,$1000                        ;800102A4[24091000,'...$']
  dc.l  $7d081240 [invalid]                ;800102A8[7D081240,'@..}']
  sllv  r9,r9,r8                          ;800102AC[01094804,'.H..']
  mtc0  0,TagLo                            ;800102B0[4080E000,'...@']
  mtc0  0,TagHi                            ;800102B4[4080E800,'...@']
  addu  r8,0,0                            ;800102B8[00004021,'!@..']
;
  cache  $01,r8($0)                        ;800102BC[BD010000,'....']
  cache  $03,r8($0)                        ;800102C0[BD030000,'....']
  addiu  r8,r8,$40                          ;800102C4[25080040,'@..%']
  bne    r8,r9,$800102bc                    ;800102C8[1509FFFC,'....']
  nop                                      ;800102CC[00000000,'....']
  jr    r31                                ;800102D0[03E00008,'....']
  nop                                      ;800102D4[00000000,'....']


while (1) {
;------------------------------------------------------------------------
    // Read one IPL block (size 0x1000) from the NAND or MemoryStick
;cache ?
    if (ReadBlock(iplBlockIdx, 0xBFD00000) < 0) {
;------------------------------------------------------------------------
        while (1); // infinite loop
L800102D8:
     }
  mfc0  r8,Config                          ;800102D8[40088000,'...@']
  addiu  r9,0,$800                          ;800102DC[24090800,'...$']
  dc.l  $7d081180 [invalid]                ;800102E0[7D081180,'...}']
  sllv  r9,r9,r8                          ;800102E4[01094804,'.H..']
  addu  r8,0,0                            ;800102E8[00004021,'!@..']
;
  cache  $14,r8($0)                         ;800102EC[BD140000,'....']
  cache  $14,r8($0)                         ;800102F0[BD140000,'....']
  addiu  r8,r8,$40                          ;800102F4[25080040,'@..%']
  bne    r8,r9,$800102ec                    ;800102F8[1509FFFC,'....']
  nop                                      ;800102FC[00000000,'....']
  jr     r31                                ;80010300[03E00008,'....']
  sync                                      ;80010304[0000000F,'....']


    // Decrypt the block in-place
;----------------------------------------------------------------------------
    if (Kirk1Decrypt(0xBFD00000, 0xBFD00000) < 0) {
;NAND reset CMD
        while (1);
;----------------------------------------------------------------------------
    }
L80010308:
;nand cmd
[$bd101008] = 0xff
;nand sts
while( [$bd101004] & 1 ==0);
;
[$bd101014] = 0x01
return


    // Read the decrypted block header
;----------------------------------------------------------------------------
    u32 dstAddress  = *(u32*)0xBFD00000; // destination address for the decrypted data
;NAND Read Sector
    u32 dataSize    = *(u32*)0xBFD00004; // size of the decrypted data (excluding the header)
;
    u32 entrypoint   = *(u32*)0xBFD00008; // the entrypoint address if this is the last block, 0 otherwise
;r4 : sector
    u32 prevChecksum = *(u32*)0xBFD0000C; // checksum of the previous block (computed below)
;r5 : data buffer
;r6 : Extra buffer
;
;----------------------------------------------------------------------------
L80010334:
;nand sts
while([$bd101004] & 1 == 0);
;
[$bd101020] = r4 << 10
[$bd101024] = $301
;80010354
while([$bd101024] & 1 == 0);
;80010364
if([$bd101028] != 0) return -1
;
   lui    r8,$bff0                          ;80010370[3C08BFF0,'...<']
;
r9  = r8[$900]
r10 = r8[$904]
r2  = r8[$908]
;
r6[0] = r9
r6[4] = r10
r6[8] = r2
;
r9 = r5
r2 = $200
L80010394:
do
{
r10 = r8[0]
r2 -= 4
r8 += 4
r9[0] = r10
r9 += 4
}while(r2);
;800103AC
return 0


    if (lastBlockChecksum != prevChecksum) {
;----------------------------------------------------------------------------
        while (1);
;rec-dev initialize
     }
;----------------------------------------------------------------------------
L800103B4:
r24 = r31
;
;rec-dev I/O init : device & clock enable ?
$800106B0(L80010ad4)
;
;reset device ?
;
[$bd20003c] = $8000
;800103D4
while( [$bd20003c] & 0x8000);
;
$80010530()
$80010508()
;
;800103F4
do{
r2 = $800105B8()
if(r2<0) continue // $800103f4
;
}while(r2 & $0080 == 0); // $800103f4
;
;  jr     r24                                ;80010410[03000008,'....']
return 0


    if (dstAddress != 0) {
;----------------------------------------------------------------------------
        // Copy the rest of the block at the specified address
;rec-dev read sector one
        // The checksum is just the XOR of the 32-bit words of the data
;
        lastBlockChecksum = _memcpy(dstAddress, 0xBFD00010, dataSize);
;a1:sector address
     }
;a2:buffer
;
;----------------------------------------------------------------------------
L80010418:
r14 = r5
; r24 = r31
;  lui    r25,$bd20
;
;read sector COMMAND ?
[$bd200030] = $00009007
;
  dc.l  $7c0428e0 [invalid]                ;8001042C[7C0428E0,'.(.|']
;
r5 >>= 8
r9 = (r4 >> 24) << 24
;
r4 = $00010020
r4 |= r9
;
;write parameter
r2 = $800104C0(r4,r5)
if(r2<0) return -1
;busy wait
$80010608()
;
;$8001045c
do{
r2 = $800105B8()
if(r2<0) return -1
r2 = r2 & $0020
}while((r2 & $0020)==0);
;
if(r2 & $0040) return -1
;
;read buffer COMMAND ?
[$bd200030] = $00002200
;
;read sector data
r2 = $800104CC(r14,$200) // r14 == r5
if(r2<0) return -1
;wait finish
r2 = $80010508()
if(r2<0) return -1
;busy wait
$80010608()
;
goto $800103f4
;
;800104B8
;  jr     r24                                ;800104B8[03000008,'....']
return -1


    if (entrypoint != 0) {
;----------------------------------------------------------------------------
        dcacheWritebackInvalidateAll();
;rec-dev : commmand output ?
        icacheWritebackInvalidateAll();
;
        jump to entrypoint;
;a1:1st write data
    }
;a2:2nd write data
;----------------------------------------------------------------------------
L800104C0:
[$bd200034] = r4
[$bd200034] = r5
goto $80010508


    iplBlockidx++;
;----------------------------------------------------------------------------
}
;rec-dev read data block
</pre>
;
;a1:distination pointer
;a2:transmit size
;
;----------------------------------------------------------------------------
L800104CC:
do{
do{
r9 = [$bd200038]
if(r9 & $0100) return -1;
}while(r9 & $4000 == 0);
;
r2 = [$bd200034]
r5 -= 4
r4[0] = r2
r4 += 4
}while(r5 >=0);
;
return 0;


=== Second version ===
;----------------------------------------------------------------------------
;wait for TX finish ?
;----------------------------------------------------------------------------
L80010508:
do{
r9 = [$bd200038]
}while(r9 & $1000 == 0);
;
if(r9 & $0300) return -1// $80010528
;
return 0


This version of the iplloader is used by Tachyon versions 0x00400000 to 0x00500000. This corresponds to the last 01g motherboard versions, and all the 02g versions except the last.
;----------------------------------------------------------------------------
;rec-dev read status ?
;----------------------------------------------------------------------------
L80010530:
;  addu  r15,r31,0                          ;80010530[03E07821,'!x..']
;  lui    r25,$bd20                          ;80010534[3C19BD20,' ..<']
;
[$bd200030] = $00008004
[$bd200034] = $06100800
[$bd200034] = 0
r2 = $80010508()
if(r2<0) return -1
;
$800105A4($80010a1c,8);
;
;get status ?
;
r4 = [$80010a1c]
r5 = [$80010a20]
;
if( (r4>>16)&0x15 != 0) return -1
;
return 0


It is very close to the original version, only some hardware initialization is modified, probably to improve stability/prevent crashes:
;----------------------------------------------------------------------------
;rec-dev read status ?
;
;a1:buffer
;a2:size
;
;----------------------------------------------------------------------------
L800105A4:
[$bd200030] = $00004000
;read sector body
return $800104cc(r4,r5)


<pre>
;-----------------------------------------------------------------------------
0xBC100058 |= 0x00800000; // (same) Enable GPIO clock
;rec-dev device ready check?
0xBC100050 |= 0x0000608E; // (same) Enable bus clock for AW (which is GE) (RegA, RegB, Edram), KIRK, NAND (EMCSM) and APB (for syscon?)
;-----------------------------------------------------------------------------
0xBC100050 &= ~0x8E;     // (new ) Disable bus clock for AW (RegA, RegB, Edram) and KIRK
L800105B8:
0xBC10004C &= ~0x408;    // (same) Clear reset for AW and KIRK
;r25 = $bd200000
0xBC100050 |= 0x0000008E; // (new ) Re-enable bus clock for AW (RegA, RegB, Edram) and KIRK
[$bd200030] = $00007001
0xBC100078 |= 2;         // (same) IO enable NAND (EMCSM)
;wait
0xBE240000 &= ~0x10;      // (same) Disable GPIO pin 4 output
do
0xBE240040 |= 0x10;      // (same) Enable GPIO pin 4 input
{
for (i = 0; i < 0x800; i++) { (void)*0xBC100040; } // (new) Read 0xBC100040 2048 times (unsure why)
r9 = [$bd200038]
sleep(1);                // (same) Wait a bit
}while(r9 & $0100);
0xBD500010 = 1;          // (same) Initialize GE Edram
if((r9 & $4000)==0) $800105c0
while (*0xBD500010 & 1 != 0) {} // (same) Wait for the Edram to be initialized
;
0xBD500040 = 1;           // (same) Finish initializing GE Edram
r2 = [$bd200034]
</pre>
r0 = [$bd200034] // ?
;800105E0
do{
r9 = [$bd200038]
if(r9 & $0100) //$80010600
{
L80010600:
return -1
}
}while( (r9 & $1000)==0);
;
return r2 & 0xff;


There are also similar minor differences in the code to initialize MemoryStick hardware.
;----------------------------------------------------------------------------
;wait for rec-dev busy
;----------------------------------------------------------------------------
L80010608:
do
{
r9 = [$bd200038]
}while( (r9 & $2000) == 0);
return


=== Third version ===  
;-----------------------------------------------------------------------------
;decrypt 1000H block
;-----------------------------------------------------------------------------
L80010620:
;  lui    r25,$bde0                          ;80010620[3C19BDE0,'...<']
;
[$bde00010] = $00000001
;r8 = r4
  dc.l  $7ca8e000 [invalid]                ;8001062C[7CA8E000,'...|']
[$bde0002c] = r8
;
;r8 = r5
  dc.l  $7c88e000 [invalid]                ;80010634[7C88E000,'...|']
[$bde00030] = r8
;
[$bde0000c] = $00000001
;$80010644
do{
r8 = [$bde0001c]
}while(r8 & $0011 == 0)
;
[$bde00028] = r8
if(r8&$0010) // $80010664
{
L80010664:
[$bde0000c] = $00000002
;$8001066C
do{
r8 = [$bde0001c]
}while(r8 & $0002 == 0)
;
[$bde00028] = r8
sync
return -1
}
;
return [$bde00014]


This version of the iplloader is present on the last 02g model and all of the 03g+ models.
;----------------------------------------------------------------------------
;transmit data with calc check sum
;
;arg1: source
;arg2: distination
;arg1: size
;
;return : 32bit check sum (add)
;
;----------------------------------------------------------------------------
L80010688:
r2 = 0 ; clear check sum
do{
r3 = r5[0]
r5 += 4
r6 -= 4
r4[0] = r3
r2 += r3 ; check sum
r4 += 4
}while(r6>=0);
return


It adds many security checks to avoid the creation of custom IPLs for these models — all of which have been broken since.
;---------------------------------------------------------------------------
;script executer
;
;a1:script pointer
;
;2 word command
;
;+00[31:28] : CMD
;+00[27: 0] : OFFSET
;+04[31: 0] : VALUE
;
;CMD : command :
; 0  : store  | [$b0000000 + OFFSET]  = VALUE
; 1  : or      | [$b0000000 + OFFSET] |= VALUE
; 2  : and    | [$b0000000 + OFFSET] &= VALUE
; 3  : wait toL| while( ( [$b0000000 + OFFSET] &= VALUE) != 0)
;(4) : wait toH| while( (~[$b0000000 + OFFSET] &= VALUE) != 0)
; 5  : delay  | for(cnt=VALUE*96;cnt;cnt--)
; F  : end    | return
;
;---------------------------------------------------------------------------
L800106B0:
r8 = r4
;addu  r25,r31,0
;
;800106b8
do{
// read CMD:offset + VALUE
r4 = r8[0]
r9 = r4 >> 28
r4 = ( (r4 << 4)>>4 ) | $b0000000
r5 = r4[4]
;
if(r9==0) //$80010724
{
;80010724
r4[0]=r5
goto $8001071c
}
if(r9==1)// $8001072c
{
;8001072C
r4[0] = r4[0] | r5
goto $8001071c
}
if(r9==2) // $8001073c
{
r4[0] = r4[0] & r5
goto $8001071c
}
r1 = 0
if(r9==3) $8001074c
;
r1 = $ffffffff ;  nor    r1,0,0
;!!!!! buggy code !!!!!
;  addiu  r10,r10,-$4
; if(r9==4) $8001074c
if(r9==7) $8001074c
;!!!!! buggy code !!!!!


List of changes:  
; if(r9==5) //$80010714
{
L80010714:
$80010768(r5);
goto L8001071C
}
;default:
;  jr    r25                                ;8001070C[03200008,'.. .']
return;
;
;case end
L8001071C:
// next script point
r8 += 8
}while(1);
;
;4,7
L8001074C:
{
do{
r9 =  (r4[0] ^ r1) & r5
}while(r9!=0);
goto $8001071c
}


* IPL load address now blacklists the CPU Scratchpad range (address & 0x1FFF0000 != 0x00010000)
;--------------------------------------------------------------------------
;delay
;--------------------------------------------------------------------------
L80010768:
r1 = ((r4 << 1) + r4)<<5 // * 96
while(r1) r1--;
return


* The mode field of the KIRK headers must have their 16 MSB set to 0, or have their LSB set to 1 (ie mode & 0xFFFF0000 == 0, or mode & 0x00010000 == 1). The second case seems to mean the XOR keys have to be applied. The 16 MSB are erased before the KIRK command is actually ran
;--------------------------------------------------------------------------
;--------------------------------------------------------------------------
L80010784:
  nop                                      ;80010784[00000000,'....']
  nop                                      ;80010788[00000000,'....']
  nop                                      ;8001078C[00000000,'....']
  nop                                      ;80010790[00000000,'....']
  nop                                      ;80010794[00000000,'....']
  nop                                      ;80010798[00000000,'....']
  nop                                      ;8001079C[00000000,'....']
  nop                                      ;800107A0[00000000,'....']
  nop                                      ;800107A4[00000000,'....']
  nop                                      ;800107A8[00000000,'....']
  nop                                      ;800107AC[00000000,'....']
  nop                                      ;800107B0[00000000,'....']
  nop                                      ;800107B4[00000000,'....']
  nop                                      ;800107B8[00000000,'....']
  nop                                      ;800107BC[00000000,'....']
  nop                                      ;800107C0[00000000,'....']
  nop                                      ;800107C4[00000000,'....']
  nop                                      ;800107C8[00000000,'....']
  nop                                      ;800107CC[00000000,'....']
  nop                                      ;800107D0[00000000,'....']
  nop                                      ;800107D4[00000000,'....']
  nop                                      ;800107D8[00000000,'....']
  nop                                      ;800107DC[00000000,'....']
  nop                                      ;800107E0[00000000,'....']
  nop                                      ;800107E4[00000000,'....']
  nop                                      ;800107E8[00000000,'....']
  nop                                      ;800107EC[00000000,'....']
  nop                                      ;800107F0[00000000,'....']
  nop                                      ;800107F4[00000000,'....']
  nop                                      ;800107F8[00000000,'....']
  nop                                      ;800107FC[00000000,'....']


* IPL blocks now must have a minimum size of 0x100 bytes (including the 16-byte header)
;-----------------------------------------------------------------------------
;
;r31 save buffer
;
L80010800:
dl 800100B0


* Blocks can be encrypted with KIRK commands 0~3 (in practice only command 1 is used)
;
;r4 save buffer
;
L80010804:
dl 0000002E
;
;read sector function entry
;
L80010808:
dl L80010194
;
;read block number
;
L8001080C:
dl L0000002E
;
;NAND Extra buffer
;
L80010810:
dl FFFFFFFF
dl 6DC64A38
dl FFFFFD89


* ECDSA verification (as specified in the KIRK header) must be enforced on the last KIRK-encrypted block
;
;NAND Data buffer (IPL FAT)
;
L8001081c
  mfhi  0                                  ;8001081C[00110010,'....']
  mflo  0                                  ;80010820[00130012,'....']
  dsllv  0,r21,0                            ;80010824[00150014,'....']
  dsrlv  0,r23,0                            ;80010828[00170016,'....']
  mult  0,r25                              ;8001082C[00190018,'....']
  div    0,r27                              ;80010830[001B001A,'....']
  nop                                      ;80010834[00000000,'....']
  nop                                      ;80010838[00000000,'....']
  nop                                      ;8001083C[00000000,'....']
  nop                                      ;80010840[00000000,'....']
  nop                                      ;80010844[00000000,'....']
  nop                                      ;80010848[00000000,'....']
  nop                                      ;8001084C[00000000,'....']
  nop                                      ;80010850[00000000,'....']
  nop                                      ;80010854[00000000,'....']
  nop                                      ;80010858[00000000,'....']
  nop                                      ;8001085C[00000000,'....']
  nop                                      ;80010860[00000000,'....']
  nop                                      ;80010864[00000000,'....']
  nop                                      ;80010868[00000000,'....']
  nop                                      ;8001086C[00000000,'....']
  nop                                      ;80010870[00000000,'....']
  nop                                      ;80010874[00000000,'....']
  nop                                      ;80010878[00000000,'....']
  nop                                      ;8001087C[00000000,'....']
  nop                                      ;80010880[00000000,'....']
  nop                                      ;80010884[00000000,'....']
  nop                                      ;80010888[00000000,'....']
  nop                                      ;8001088C[00000000,'....']
  nop                                      ;80010890[00000000,'....']
  nop                                      ;80010894[00000000,'....']
  nop                                      ;80010898[00000000,'....']
  nop                                      ;8001089C[00000000,'....']
  nop                                      ;800108A0[00000000,'....']
  nop                                      ;800108A4[00000000,'....']
  nop                                      ;800108A8[00000000,'....']
  nop                                      ;800108AC[00000000,'....']
  nop                                      ;800108B0[00000000,'....']
  nop                                      ;800108B4[00000000,'....']
  nop                                      ;800108B8[00000000,'....']
  nop                                      ;800108BC[00000000,'....']
  nop                                      ;800108C0[00000000,'....']
  nop                                      ;800108C4[00000000,'....']
  nop                                      ;800108C8[00000000,'....']
  nop                                      ;800108CC[00000000,'....']
  nop                                      ;800108D0[00000000,'....']
  nop                                      ;800108D4[00000000,'....']
  nop                                      ;800108D8[00000000,'....']
  nop                                      ;800108DC[00000000,'....']
  nop                                      ;800108E0[00000000,'....']
  nop                                      ;800108E4[00000000,'....']
  nop                                      ;800108E8[00000000,'....']
  nop                                      ;800108EC[00000000,'....']
  nop                                      ;800108F0[00000000,'....']
  nop                                      ;800108F4[00000000,'....']
  nop                                      ;800108F8[00000000,'....']
  nop                                      ;800108FC[00000000,'....']
  nop                                      ;80010900[00000000,'....']
  nop                                      ;80010904[00000000,'....']
  nop                                      ;80010908[00000000,'....']
  nop                                      ;8001090C[00000000,'....']
  nop                                      ;80010910[00000000,'....']
  nop                                      ;80010914[00000000,'....']
  nop                                      ;80010918[00000000,'....']
  nop                                      ;8001091C[00000000,'....']
  nop                                      ;80010920[00000000,'....']
  nop                                      ;80010924[00000000,'....']
  nop                                      ;80010928[00000000,'....']
  nop                                      ;8001092C[00000000,'....']
  nop                                      ;80010930[00000000,'....']
  nop                                      ;80010934[00000000,'....']
  nop                                      ;80010938[00000000,'....']
  nop                                      ;8001093C[00000000,'....']
  nop                                      ;80010940[00000000,'....']
  nop                                      ;80010944[00000000,'....']
  nop                                      ;80010948[00000000,'....']
  nop                                      ;8001094C[00000000,'....']
  nop                                      ;80010950[00000000,'....']
  nop                                      ;80010954[00000000,'....']
  nop                                      ;80010958[00000000,'....']
  nop                                      ;8001095C[00000000,'....']
  nop                                      ;80010960[00000000,'....']
  nop                                      ;80010964[00000000,'....']
  nop                                      ;80010968[00000000,'....']
  nop                                      ;8001096C[00000000,'....']
  nop                                      ;80010970[00000000,'....']
  nop                                      ;80010974[00000000,'....']
  nop                                      ;80010978[00000000,'....']
  nop                                      ;8001097C[00000000,'....']
  nop                                      ;80010980[00000000,'....']
  nop                                      ;80010984[00000000,'....']
  nop                                      ;80010988[00000000,'....']
  nop                                      ;8001098C[00000000,'....']
  nop                                      ;80010990[00000000,'....']
  nop                                      ;80010994[00000000,'....']
  nop                                      ;80010998[00000000,'....']
  nop                                      ;8001099C[00000000,'....']
  nop                                      ;800109A0[00000000,'....']
  nop                                      ;800109A4[00000000,'....']
  nop                                      ;800109A8[00000000,'....']
  nop                                      ;800109AC[00000000,'....']
  nop                                      ;800109B0[00000000,'....']
  nop                                      ;800109B4[00000000,'....']
  nop                                      ;800109B8[00000000,'....']
  nop                                      ;800109BC[00000000,'....']
  nop                                      ;800109C0[00000000,'....']
  nop                                      ;800109C4[00000000,'....']
  nop                                      ;800109C8[00000000,'....']
  nop                                      ;800109CC[00000000,'....']
  nop                                      ;800109D0[00000000,'....']
  nop                                      ;800109D4[00000000,'....']
  nop                                      ;800109D8[00000000,'....']
  nop                                      ;800109DC[00000000,'....']
  nop                                      ;800109E0[00000000,'....']
  nop                                      ;800109E4[00000000,'....']
  nop                                      ;800109E8[00000000,'....']
  nop                                      ;800109EC[00000000,'....']
  nop                                      ;800109F0[00000000,'....']
  nop                                      ;800109F4[00000000,'....']
  nop                                      ;800109F8[00000000,'....']
  nop                                      ;800109FC[00000000,'....']
  nop                                      ;80010A00[00000000,'....']
  nop                                      ;80010A04[00000000,'....']
  nop                                      ;80010A08[00000000,'....']
  nop                                      ;80010A0C[00000000,'....']
  nop                                      ;80010A10[00000000,'....']
  nop                                      ;80010A14[00000000,'....']
  nop                                      ;80010A18[00000000,'....']
;
;rec-dev status read buffer ?
L80010A1C:
dl 00000000,00000000
;
  nop                                      ;80010A24[00000000,'....']
  nop                                      ;80010A28[00000000,'....']
  nop                                      ;80010A2C[00000000,'....']
  nop                                      ;80010A30[00000000,'....']
  nop                                      ;80010A34[00000000,'....']
  nop                                      ;80010A38[00000000,'....']
  nop                                      ;80010A3C[00000000,'....']
  nop                                      ;80010A40[00000000,'....']
  nop                                      ;80010A44[00000000,'....']
  nop                                      ;80010A48[00000000,'....']
  nop                                      ;80010A4C[00000000,'....']
  nop                                      ;80010A50[00000000,'....']
  nop                                      ;80010A54[00000000,'....']
  nop                                      ;80010A58[00000000,'....']
  nop                                      ;80010A5C[00000000,'....']
  nop                                      ;80010A60[00000000,'....']
  nop                                      ;80010A64[00000000,'....']
  nop                                      ;80010A68[00000000,'....']
  nop                                      ;80010A6C[00000000,'....']
  nop                                      ;80010A70[00000000,'....']
  nop                                      ;80010A74[00000000,'....']
  nop                                      ;80010A78[00000000,'....']
  nop                                      ;80010A7C[00000000,'....']
;
;
;script command : I/O init
;
L80010A80:
dl 1C100058,00800000 ; [$bc100058] |= 00800000
dl 1C100050,0000608E ; [$bc100050] |= 0000608E
dl 2C10004C,FFFFFBF7 ; [$bc10004C] &= FFFFFBF7
dl 1C100078,00000002 ; [$bc100078] |= 00000002
dl 2E240000,FFFFFFEF ; [$be240000] &= FFFFFFEF : GPIO bit4 direction read?
dl 1E240040,00000010 ; [$be240040] |= 00000010 : GPIO bit4 pullup enable ?
dl 50000000,00000001 ; delay 1
dl 0D500010,00000001 ; [$bd500010] = 00000001
dl 3D500010,00000001 ; while( [$bd500010] & 1)
dl 0D500040,00000001 ; [$bd500040] = 00000001
dl F0000000 ; end
;
;script command : rec-dev I/O init
;
L80010AD4:
dl 3D500010,00000001 ; while( [$bd500010] & 1)
dl 1C100054,00000100 ; [$bc100054] |= 00000100
dl 1C100050,00000400 ; [$bc100050] |= 00000400
dl 1C100078,00000010 ; [$bc100078] |= 00000010
dl 2C10004C,FFFFFEFF ; [$bc10004C] &= FFFFFEFF
dl F0000000 ; end
;
;------------------------------------------------------------------------------
;code end
;------------------------------------------------------------------------------
</pre>


* The last 0x20 bytes of the block contain a SHA1 hash encrypted with KIRK command 7 and keyseed = 0x6C.
=== 03g+ ===


The block hash is calculated using: <pre>sha1(block[8:] + block[:8])</pre>
Known changes from earlier Tachyon ROM Revisions:  
Which means the first 8 bytes of the payload (containing the first half of the block header) are copied to the end before the block is hashed.


The last block hash remains in memory and is xored in each SHA1 for each block, the result is used for the hash compare in the Kirk command 0x10 ECDSA check.
* IPL Load address now blacklists the CPU Scratchpad range (0x80010000/0xA0010000)
<pre>
ROM:8001016C                ext    $t0, $a0, 0x10, 0xD
ROM:80010170                xori    $t0, 1
ROM:80010174                beqz    $t0, loc_800101F0
</pre>


* The XOR of all the blocks' hashes (as computed in the previous step) is computed, and in the last block (entrypoint != 0), offset 0xFA0 contains a signature of this value, which is verified with KIRK command 0x11 using a custom public key stored inside the ROM (0xbc660611a70bd7f2d140a48215c096d11d2d4112, 0xf0e9379ac4e0d387c542d091349dd15169dd5a87).
* IPL Entrypoint address now blacklists specific ranges (0xBFD00000...)


* The first 0x10 bytes of IPL blocks are, before decryption, xored using a XOR key, stored in the ROM and selected from a value stored in the NAND spare data (see [[NAND Flash Memory]] for more details), or set to 0 for jigkick boot. If this value is zero, the XOR key step is skipped (probably to allow compatibility of the Jig Memory Stick across all devices). Otherwise, bits 0..4 of that value are used as an index on the XOR key table, and bits 5..11 are used as an additional rotation index. In practice, this value is 1 for all targets except 05g, and 05g uses the value 2.
* IPL blocks now must have a minimum size of 0x100 bytes


==== Vulnerabilities ====
* Kirk command 1 ECDSA is now enforced on IPL blocks


Since the KIRK command 1 ECDSA private key & encryption key were known, the main issues for dumping the iplloader were the XOR keys, the hash check, and the additional ECDSA check on the XOR of hashes.
* The last 0x20 bytes of the block contain a SHA1 hash encrypted with Kirk command 7 0x6C


The XOR keys are easy to disable if you can enable jigkick, which had been done for 03g before Davee dumped the iplloader. (Note that now that we know that it can be disabled writing the appropriate data in the NAND spare data, it could also be circumvented that way.)
The block hash is calculated using
<pre>
sha1(block[ 8 : ] + block [ : 8])
</pre>
The first 8 bytes of the payload are copied to the end before the block is hashed.


The hash check would've been easy to solve, but the fact the block was rotated before being hashed was unknown. Davee glitched this check in order to pass.
The last block hash remains in memory and is xored in each SHA1 for each block, the result is used for the hash compare in the Kirk command 0x10 ECDSA check.


For the additional check, it could be skipped using a clever trick: if you set 0xBC10004C as the destination address of the IPL block, then the CPU resets and remaps 0xBFD00000 to 0xBFC00000 then runs back at 0xBFC00000. Since 0xBFD00000 is used as a temporary space for the decrypted IPL, it means you can easily achieve code execution. Davee used this method to dump the iplloader payload, which contains all the relevant information.
* Kirk command 0x11 ECDSA check using a custom public key (stored inside the ROM) and the xored SHA1 sum of all blocks in the hash compare function, signature present in the last IPL block (entrypoint != 0)


Now that the code is known, the two first issues are very easy to handle. For the last one, an easy trick is that the signature is on the XOR of the SHA1's, which means if you place your own block twice before a legitimate IPL, the two SHA1's will cancel each other and the signature check will pass.
* The first 0x10 bytes of IPL blocks are xored using a XOR key (stored in ROM and selected from an index written by updater on nand, index 1 is used for all targets except 05g, 05g uses index 2). The XOR step is not performed when Jig/Service Mode is enabled, to allow compatibility of the Jig Memory Stick across all devices.


== PS Vita Compatibility mode behaviour ==  
== PS Vita Compatibility mode behaviour ==  


On PS Vita, PSP iplloader is sent by the <b>Compatibility Security Module</b> (<code>os0:sm/compat_sm.self</code>) to the non-secure ARM kernel, which writes it to <b>0xE8100000</b> (<i>named CompatSharedSram and mapped to the <b>0xBFC00000</b> reset vector on the emulated PSP/Tachyon side</i>).
On PS Vita, PSP iplloader is sent by the Compatibility security module (os0:sm/compat_sm.self) to the non-secure ARM kernel which writes it to 0xE8100000 (named CompatSharedSram and mapped to the 0xBFC00000 reset vector on the emulated PSP/Tachyon side).


Then, <code>compat_sm.self</code> sends a specific 0x40-bytes XOR key to be used by the PS Vita's iplloader as a 0x40 bytes XOR mask against the IPL header. The IPL is stored in the <code>pcbc.skprx</code> kernel module.
compat_sm then sends a specific 0x40-bytes XOR key to be used by the PS Vita's iplloader as a 0x40 bytes XOR mask against the IPL header. The IPL is stored in the pcbc.skprx kernel module.


[[Kirk]] command 1 is then used on the result. Unlike actual PSP units, the IPL is decrypting in a single large block, rather than in multiple blocks.
Kirk command 1 is then used on the result. Unlike on actual PSP units, the IPL is decrypted in a single large block rather than in multiple blocks.


The 0x40 bytes key gets updated depending on the firmware version in use.
The 0x40 bytes key gets updated depending on the firmware version in use.


A 0x40 bytes XOR mask is also part of the 3.50+ DTP-T1000 security [[#Behaviour|(<i>see PSP iplloader section</i>)]].
A 0x40 bytes XOR mask is also part of the 3.50+ DTP-T1000 security. See PSP iplloader section.


= Dumper =
= Dumper =


As of March 21st 2018, a dumper for DTP-T1000 iplloader has been made by mathieulh, [https://github.com/mathieulh/DTP-T1000-Pre-IPL-dumper it is available on GitHub].
As of March 21st 2018, a dumper for DTP-T1000 iplloader has been made available on github by mathieulh:
* [https://github.com/mathieulh/DTP-T1000-Pre-IPL-dumper]


= Trivia =
= See also =


On PSP iplloader versions 0.7 and later, the build number/date/version is copied from 0xBFC00FFC to ctc0  $17
<pre>
lw      $t0, 0xBFC00FFC
ctc0    $t0, $17
</pre>
= See also =
* [[Initial Program Loader]]
* [https://web.archive.org/web/20090826053327/http://silverspring.lan.st/NPSPTD_01.txt iplloader and IPL descriptions by SilverSpring]
* [https://web.archive.org/web/20090826053327/http://silverspring.lan.st/NPSPTD_01.txt iplloader and IPL descriptions by SilverSpring]
Please note that all contributions to PSP Developer wiki are considered to be released under the GNU Free Documentation License 1.2 (see PSP Developer wiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!

To protect the wiki against automated edit spam, we kindly ask you to solve the following hCaptcha:

Cancel Editing help (opens in new window)