mini/boot2.c
2009-05-15 05:33:08 -07:00

381 lines
9.4 KiB
C

/*
mini - a Free Software replacement for the Nintendo/BroadOn IOS.
boot2 chainloader
Copyright (C) 2008, 2009 marcan (TODO: change name?, add email?,...)
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
*/
#include "types.h"
#include "nand.h"
#include "memory.h"
#include "crypto.h"
#include "string.h"
#include "gecko.h"
#include "powerpc.h"
#include "utils.h"
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);
static u8 boot2_initialized = 0;
static u8 boot2_copy;
static u8 pages_read;
static u8 *page_ptr;
extern void *vector;
typedef struct {
u32 len;
u32 data_offset;
u32 certs_len;
u32 tik_len;
u32 tmd_len;
u32 padding[3];
} boot2header;
typedef struct {
u32 hdrsize;
u32 loadersize;
u32 elfsize;
u32 argument;
} ioshdr;
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;
u8 iv[16];
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);
nand_wait();
// 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;
}
}
} 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;
}
gecko_printf("boot2 blocks:");
for(block=0; block<valid_blocks; block++)
gecko_printf(" %02x", boot2_blocks[block]);
gecko_printf("\n");
// 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));
memset(iv, 0, 16);
memcpy(iv, &boot2_tik.titleid, 8);
aes_reset();
aes_set_iv(iv);
aes_set_key(otp.common_key);
memcpy(boot2_key, &boot2_tik.cipher_title_key, 16);
aes_decrypt(boot2_key, boot2_key, 1, 0);
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];
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();
boot2_initialized = 1;
}
static u32 match[] = {
0xF7FFFFB8,
0xBC024708,
1,
2,
};
static u32 patch[] = {
0xF7FFFFB8,
0xBC024708,
0x10001,
0x48415858,
};
void boot2_run(u32 tid_hi, u32 tid_lo) {
u8 *ptr;
int i;
ioshdr *hdr;
patch[2] = tid_hi;
patch[3] = tid_lo;
gecko_printf("booting boot2 with title %08x-%08x\n", tid_hi, tid_lo);
mem_protect(1, (void *)0x11000000, (void *)0x13FFFFFF);
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;
for (i = 0; i < sizeof(boot2); i += 1) {
if (memcmp(ptr+i, match, sizeof(match)) == 0) {
memcpy(ptr+i, patch, sizeof(patch));
gecko_printf("patched data @%08x\n", (u32)ptr+i);
}
}
hdr->argument = 0x42;
vector = (u8 *)0x11000000 + hdr->hdrsize;
gecko_printf("boot2 is at %p\n", vector);
return;
}
void boot2_ipc(volatile ipc_request *req)
{
switch (req->req) {
case IPC_BOOT2_RUN:
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();
boot2_run((u32)req->args[0], (u32)req->args[1]);
} else {
ipc_post(req->code, req->tag, 1, -1);
}
break;
default:
gecko_printf("IPC: unknown SLOW BOOT2 request %04X\n", req->req);
}
}