2009-04-03 16:27:19 +02:00
/*
mini - a Free Software replacement for the Nintendo / BroadOn IOS .
boot2 chainloader
2009-04-10 18:15:10 +02:00
Copyright ( C ) 2008 , 2009 Hector Martin " marcan " < marcan @ marcansoft . com >
2009-04-03 16:27:19 +02:00
Copyright ( C ) 2008 , 2009 Sven Peter < svenpeter @ gmail . com >
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , version 2.
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
2009-02-24 16:20:28 +01:00
# include "types.h"
# include "nand.h"
# include "memory.h"
# include "crypto.h"
# include "string.h"
# include "gecko.h"
# include "powerpc.h"
2009-03-18 18:21:00 +01:00
# include "utils.h"
2009-02-24 16:20:28 +01:00
2009-03-18 18:21:00 +01:00
static u8 boot2 [ 0x80000 ] MEM2_BSS ALIGNED ( 64 ) ;
static u8 boot2_key [ 32 ] MEM2_BSS ALIGNED ( 32 ) ;
static u8 boot2_iv [ 32 ] MEM2_BSS ALIGNED ( 32 ) ;
static u8 sector_buf [ PAGE_SIZE ] MEM2_BSS ALIGNED ( 64 ) ;
static u8 ecc_buf [ ECC_BUFFER_ALLOC ] MEM2_BSS ALIGNED ( 128 ) ;
2009-02-24 16:20:28 +01:00
static u8 boot2_initialized = 0 ;
2009-03-18 18:21:00 +01:00
static u8 boot2_copy ;
static u8 pages_read ;
static u8 * page_ptr ;
2009-02-24 16:20:28 +01:00
2009-03-18 18:21:00 +01:00
typedef struct {
2009-02-24 16:20:28 +01:00
u32 len ;
2009-03-18 18:21:00 +01:00
u32 data_offset ;
2009-02-24 16:20:28 +01:00
u32 certs_len ;
u32 tik_len ;
u32 tmd_len ;
2009-03-18 18:21:00 +01:00
u32 padding [ 3 ] ;
} boot2header ;
2009-02-24 16:20:28 +01:00
typedef struct {
u32 hdrsize ;
u32 loadersize ;
u32 elfsize ;
u32 argument ;
} ioshdr ;
2009-03-18 18:21:00 +01:00
typedef struct {
u64 signature ;
u32 generation ;
u8 blocks [ 0x40 ] ;
} __attribute__ ( ( packed ) ) boot2blockmap ;
typedef struct {
u32 cid ;
u16 index ;
u16 type ;
u64 size ;
u8 hash [ 20 ] ;
} __attribute__ ( ( packed ) ) tmd_content ;
typedef struct {
u32 type ;
u8 sig [ 256 ] ;
u8 fill [ 60 ] ;
} __attribute__ ( ( packed ) ) sig_rsa2048 ;
typedef struct {
sig_rsa2048 signature ;
char issuer [ 0x40 ] ;
u8 version ;
u8 ca_crl_version ;
u8 signer_crl_version ;
u8 fill2 ;
u64 sys_version ;
u64 title_id ;
u32 title_type ;
u16 group_id ;
u16 zero ;
u16 region ;
u8 ratings [ 16 ] ;
u8 reserved [ 42 ] ;
u32 access_rights ;
u16 title_version ;
u16 num_contents ;
u16 boot_index ;
u16 fill3 ;
tmd_content boot_content ;
} __attribute__ ( ( packed ) ) tmd ;
typedef struct _tik {
sig_rsa2048 signature ;
char issuer [ 0x40 ] ;
u8 fill [ 63 ] ;
u8 cipher_title_key [ 16 ] ;
u8 fill2 ;
u64 ticketid ;
u32 devicetype ;
u64 titleid ;
u16 access_mask ;
u8 reserved [ 0x3c ] ;
u8 cidx_mask [ 0x40 ] ;
u16 padding ;
u32 limits [ 16 ] ;
} __attribute__ ( ( packed ) ) tik ;
static boot2blockmap good_blockmap MEM2_BSS ;
# define BLOCKMAP_SIGNATURE 0x26f29a401ee684cfULL
# define BOOT2_START 1
# define BOOT2_END 7
static u8 boot2_blocks [ BOOT2_END - BOOT2_START + 1 ] ;
static u32 valid_blocks ;
static tmd boot2_tmd MEM2_BSS ;
static tik boot2_tik MEM2_BSS ;
static u8 * boot2_content ;
static u32 boot2_content_size ;
// find two equal valid blockmaps from a set of three, return one of them
static int find_valid_map ( const boot2blockmap * maps )
{
if ( maps [ 0 ] . signature = = BLOCKMAP_SIGNATURE ) {
if ( ! memcmp ( & maps [ 0 ] , & maps [ 1 ] , sizeof ( boot2blockmap ) ) )
return 0 ;
if ( ! memcmp ( & maps [ 0 ] , & maps [ 2 ] , sizeof ( boot2blockmap ) ) )
return 0 ;
}
if ( maps [ 1 ] . signature = = BLOCKMAP_SIGNATURE ) {
if ( ! memcmp ( & maps [ 1 ] , & maps [ 2 ] , sizeof ( boot2blockmap ) ) )
return 1 ;
}
return - 1 ;
}
// translate a page offset into boot2 to a real NAND page number using blockmap
static inline u32 boot2_page_translate ( u32 page )
{
u32 subpage = page % BLOCK_SIZE ;
u32 block = page / BLOCK_SIZE ;
return boot2_blocks [ block ] * BLOCK_SIZE + subpage ;
}
// read boot2 up to the specified number of bytes (aligned to the next page)
static int read_to ( u32 bytes )
{
if ( bytes > ( valid_blocks * BLOCK_SIZE * PAGE_SIZE ) ) {
gecko_printf ( " tried to read %d boot2 bytes (%d pages), but only %d blocks (%d pages) are valid! \n " , bytes , ( bytes + ( PAGE_SIZE - 1 ) ) / PAGE_SIZE , valid_blocks , valid_blocks * BLOCK_SIZE ) ;
return - 1 ;
}
while ( bytes > pages_read * PAGE_SIZE ) {
u32 page = boot2_page_translate ( pages_read ) ;
nand_read_page ( page , page_ptr , ecc_buf ) ;
nand_wait ( ) ;
if ( nand_correct ( page , page_ptr , ecc_buf ) < 0 ) {
gecko_printf ( " boot2 page %d (NAND 0x%x) is uncorrectable \n " , pages_read , page ) ;
return - 1 ;
}
page_ptr + = PAGE_SIZE ;
pages_read + + ;
}
return 0 ;
}
int boot2_load ( int copy )
{
boot2blockmap * maps = ( boot2blockmap * ) sector_buf ;
u32 block ;
u32 page ;
int mapno ;
u32 found = 0 ;
boot2header * hdr ;
2009-02-24 16:20:28 +01:00
u8 iv [ 16 ] ;
2009-03-18 18:21:00 +01:00
boot2_content = NULL ;
boot2_content_size = 0 ;
pages_read = 0 ;
memset ( & good_blockmap , 0 , sizeof ( boot2blockmap ) ) ;
valid_blocks = 0 ;
// find the best blockmap
for ( block = BOOT2_START ; block < = BOOT2_END ; block + + ) {
page = ( block + 1 ) * BLOCK_SIZE - 1 ;
nand_read_page ( page , sector_buf , ecc_buf ) ;
2009-02-26 09:54:15 +01:00
nand_wait ( ) ;
2009-03-18 18:21:00 +01:00
// boot1 doesn't actually do this, but it's probably a good idea to try to correct 1-bit errors anyway
if ( nand_correct ( page , sector_buf , ecc_buf ) < 0 ) {
gecko_printf ( " boot2 map candidate page %d is uncorrectable, trying anyway \n " , page ) ;
}
mapno = find_valid_map ( maps ) ;
if ( mapno > = 0 ) {
gecko_printf ( " found valid boot2 blockmap at page 0x%x, submap %d, generation %d \n " , page , mapno , maps [ mapno ] . generation ) ;
if ( maps [ mapno ] . generation > = good_blockmap . generation ) {
memcpy ( & good_blockmap , & maps [ mapno ] , sizeof ( boot2blockmap ) ) ;
found = 1 ;
}
}
}
if ( ! found ) {
gecko_printf ( " no valid boot2 blockmap found! \n " ) ;
return - 1 ;
}
// traverse the blockmap and make a list of the actual boot2 blocks, in order
if ( copy = = 0 ) {
for ( block = BOOT2_START ; block < = BOOT2_END ; block + + ) {
if ( good_blockmap . blocks [ block ] = = 0x00 ) {
boot2_blocks [ valid_blocks + + ] = block ;
}
2009-03-07 17:31:45 +01:00
}
2009-03-18 18:21:00 +01:00
} else if ( copy = = 1 ) {
for ( block = BOOT2_END ; block > = BOOT2_START ; block - - ) {
if ( good_blockmap . blocks [ block ] = = 0x00 ) {
boot2_blocks [ valid_blocks + + ] = block ;
}
}
} else {
gecko_printf ( " invalid boot2 copy %d \n " , copy ) ;
return - 1 ;
2009-02-24 16:20:28 +01:00
}
2009-03-18 18:21:00 +01:00
gecko_printf ( " boot2 blocks: " ) ;
for ( block = 0 ; block < valid_blocks ; block + + )
gecko_printf ( " %02x " , boot2_blocks [ block ] ) ;
gecko_printf ( " \n " ) ;
2009-02-24 16:20:28 +01:00
2009-03-18 18:21:00 +01:00
// read boot2 header
page_ptr = boot2 ;
if ( read_to ( sizeof ( boot2header ) ) < 0 ) {
gecko_printf ( " error while reading boot2 header " ) ;
return - 1 ;
}
hdr = ( boot2header * ) boot2 ;
if ( hdr - > len ! = sizeof ( boot2header ) ) {
gecko_printf ( " invalid boot2 header size 0x%x \n " , hdr - > len ) ;
return - 1 ;
}
if ( hdr - > tmd_len ! = sizeof ( tmd ) ) {
gecko_printf ( " boot2 tmd size mismatch: expected 0x%x, got 0x%x (more than one content?) \n " , sizeof ( tmd ) , hdr - > tmd_len ) ;
return - 1 ;
}
if ( hdr - > tik_len ! = sizeof ( tik ) ) {
gecko_printf ( " boot2 tik size mismatch: expected 0x%x, got 0x%x \n " , sizeof ( tik ) , hdr - > tik_len ) ;
return - 1 ;
}
// read tmd, tik, certs
if ( read_to ( hdr - > data_offset ) < 0 ) {
gecko_printf ( " error while reading boot2 certs/tmd/ticket " ) ;
return - 1 ;
}
memcpy ( & boot2_tik , & boot2 [ hdr - > len + hdr - > certs_len ] , sizeof ( tik ) ) ;
memcpy ( & boot2_tmd , & boot2 [ hdr - > len + hdr - > certs_len + hdr - > tik_len ] , sizeof ( tmd ) ) ;
2009-02-24 16:20:28 +01:00
memset ( iv , 0 , 16 ) ;
2009-03-18 18:21:00 +01:00
memcpy ( iv , & boot2_tik . titleid , 8 ) ;
2009-02-24 16:20:28 +01:00
aes_reset ( ) ;
aes_set_iv ( iv ) ;
aes_set_key ( otp . common_key ) ;
2009-03-18 18:21:00 +01:00
memcpy ( boot2_key , & boot2_tik . cipher_title_key , 16 ) ;
2009-02-24 16:20:28 +01:00
2009-03-18 18:21:00 +01:00
aes_decrypt ( boot2_key , boot2_key , 1 , 0 ) ;
2009-02-24 16:20:28 +01:00
2009-03-18 18:21:00 +01:00
memset ( boot2_iv , 0 , 16 ) ;
memcpy ( boot2_iv , & boot2_tmd . boot_content . index , 2 ) ; //just zero anyway...
u32 * kp = ( u32 * ) boot2_key ;
gecko_printf ( " boot2 title key: %08x%08x%08x%08x \n " , kp [ 0 ] , kp [ 1 ] , kp [ 2 ] , kp [ 3 ] ) ;
boot2_content_size = ( boot2_tmd . boot_content . size + 15 ) & ~ 15 ;
gecko_printf ( " boot2 content size: 0x%x (padded: 0x%x) \n " , ( u32 ) boot2_tmd . boot_content . size , boot2_content_size ) ;
// read content
if ( read_to ( hdr - > data_offset + boot2_content_size ) < 0 ) {
gecko_printf ( " error while reading boot2 content " ) ;
return - 1 ;
}
boot2_content = & boot2 [ hdr - > data_offset ] ;
2009-02-24 16:20:28 +01:00
2009-03-18 18:21:00 +01:00
boot2_copy = copy ;
gecko_printf ( " boot2 copy %d loaded to %p \n " , copy , boot2 ) ;
return 0 ;
}
void boot2_init ( ) {
boot2_copy = - 1 ;
boot2_initialized = 0 ;
if ( boot2_load ( 0 ) < 0 ) {
gecko_printf ( " failed to load boot2 copy 0, trying copy 1... \n " ) ;
if ( boot2_load ( 1 ) < 0 ) {
gecko_printf ( " failed to load boot2 copy 1! \n " ) ;
return ;
}
}
// boot2 content flush would flush entire cache anyway so just do it all
dc_flushall ( ) ;
2009-02-24 16:20:28 +01:00
boot2_initialized = 1 ;
}
static u32 match [ ] = {
0xF7FFFFB8 ,
0xBC024708 ,
1 ,
2 ,
} ;
static u32 patch [ ] = {
0xF7FFFFB8 ,
0xBC024708 ,
0x10001 ,
0x48415858 ,
} ;
2009-04-10 17:25:13 +02:00
u32 boot2_run ( u32 tid_hi , u32 tid_lo ) {
2009-03-18 18:21:00 +01:00
u8 * ptr ;
2009-02-24 16:20:28 +01:00
int i ;
2009-03-18 18:21:00 +01:00
ioshdr * hdr ;
2009-02-24 16:20:28 +01:00
patch [ 2 ] = tid_hi ;
patch [ 3 ] = tid_lo ;
2009-03-18 18:21:00 +01:00
gecko_printf ( " booting boot2 with title %08x-%08x \n " , tid_hi , tid_lo ) ;
2009-02-26 21:21:33 +01:00
mem_protect ( 1 , ( void * ) 0x11000000 , ( void * ) 0x13FFFFFF ) ;
2009-03-18 18:21:00 +01:00
aes_reset ( ) ;
aes_set_iv ( boot2_iv ) ;
aes_set_key ( boot2_key ) ;
aes_decrypt ( boot2_content , ( void * ) 0x11000000 , boot2_content_size / 16 , 0 ) ;
hdr = ( ioshdr * ) 0x11000000 ;
ptr = ( u8 * ) 0x11000000 + hdr - > hdrsize + hdr - > loadersize ;
2009-02-24 16:20:28 +01:00
for ( i = 0 ; i < sizeof ( boot2 ) ; i + = 1 ) {
if ( memcmp ( ptr + i , match , sizeof ( match ) ) = = 0 ) {
memcpy ( ptr + i , patch , sizeof ( patch ) ) ;
2009-02-26 09:54:15 +01:00
gecko_printf ( " patched data @%08x \n " , ( u32 ) ptr + i ) ;
2009-02-24 16:20:28 +01:00
}
}
hdr - > argument = 0x42 ;
2009-04-10 17:25:13 +02:00
u32 vector = 0x11000000 + hdr - > hdrsize ;
gecko_printf ( " boot2 is at 0x%08x \n " , vector ) ;
return vector ;
2009-02-24 16:20:28 +01:00
}
2009-02-26 09:54:15 +01:00
2009-04-10 17:25:13 +02:00
u32 boot2_ipc ( volatile ipc_request * req )
2009-02-26 09:54:15 +01:00
{
2009-04-10 17:25:13 +02:00
u32 vector = 0 ;
2009-02-26 09:54:15 +01:00
switch ( req - > req ) {
case IPC_BOOT2_RUN :
2009-03-18 18:21:00 +01:00
if ( boot2_initialized ) {
// post first so that the memory protection doesn't kill IPC for the PowerPC
ipc_post ( req - > code , req - > tag , 1 , boot2_copy ) ;
ipc_flush ( ) ;
2009-04-10 17:25:13 +02:00
vector = boot2_run ( ( u32 ) req - > args [ 0 ] , ( u32 ) req - > args [ 1 ] ) ;
2009-03-18 18:21:00 +01:00
} else {
ipc_post ( req - > code , req - > tag , 1 , - 1 ) ;
}
2009-04-10 17:25:13 +02:00
2009-04-07 21:27:34 +02:00
break ;
case IPC_BOOT2_TMD :
2009-04-10 17:25:13 +02:00
if ( boot2_initialized )
2009-04-07 21:27:34 +02:00
ipc_post ( req - > code , req - > tag , 1 , & boot2_tmd ) ;
2009-04-10 17:25:13 +02:00
else
2009-04-07 21:27:34 +02:00
ipc_post ( req - > code , req - > tag , 1 , - 1 ) ;
2009-04-10 17:25:13 +02:00
2009-02-26 09:54:15 +01:00
break ;
2009-04-07 21:27:34 +02:00
2009-02-26 09:54:15 +01:00
default :
gecko_printf ( " IPC: unknown SLOW BOOT2 request %04X \n " , req - > req ) ;
}
2009-04-10 17:25:13 +02:00
return vector ;
2009-02-26 09:54:15 +01:00
}