diff --git a/utils/AMS/hacking/Makefile b/utils/AMS/hacking/Makefile index 7a10c20..5dc615a 100644 --- a/utils/AMS/hacking/Makefile +++ b/utils/AMS/hacking/Makefile @@ -10,25 +10,36 @@ OUTFILE=patched.bin all: amsinfo $(OUTFILE) amsinfo: amsinfo.c - gcc -o amsinfo -W -Wall amsinfo.c + gcc -o amsinfo -W -Wall -Werror amsinfo.c mkamsboot: mkamsboot.c - gcc -o mkamsboot -W -Wall mkamsboot.c + gcc -o mkamsboot -W -Wall -Werror mkamsboot.c # Rules for our test ARM application - assemble, link, then extract # the binary code -test.o: test.S - arm-elf-as -o test.o test.S +stage1.o: stage1.S + arm-elf-as -o stage1.o stage1.S -test.elf: test.o - arm-elf-ld -e 0 -o test.elf test.o +stage1.elf: stage1.o + arm-elf-ld -e 0 -o stage1.elf stage1.o -test.bin: test.elf - arm-elf-objcopy -O binary test.elf test.bin +stage1.bin: stage1.elf + arm-elf-objcopy -O binary stage1.elf stage1.bin -$(OUTFILE): mkamsboot test.bin $(INFILE) - ./mkamsboot $(INFILE) test.bin $(OUTFILE) +crt0.o: crt0.S + arm-elf-as crt0.S -o crt0.o + +stage2.o: stage2.c stage2.h + arm-elf-gcc -c -Os -fomit-frame-pointer -o stage2.o stage2.c + +find-offset: find-offset.c + gcc -o find-offset -W -Wall -Werror find-offset.c + +$(OUTFILE): mkamsboot stage1.bin stage2.o crt0.o $(INFILE) find-offset + arm-elf-ld -e 0 -o stage2.elf crt0.o stage2.o + arm-elf-objcopy -O binary stage2.elf stage2.bin + ./mkamsboot $(INFILE) stage1.bin stage2.bin `./find-offset $(INFILE)` $(OUTFILE) clean: - rm -fr amsinfo mkamsboot test.bin test.o test.elf $(OUTFILE) *~ + rm -fr amsinfo mkamsboot stage{1,2}.bin stage{1,2}.o crt0.o stage{1,2}.elf $(OUTFILE) find-offset *~ diff --git a/utils/AMS/hacking/crt0.S b/utils/AMS/hacking/crt0.S new file mode 100644 index 0000000..8582148 --- /dev/null +++ b/utils/AMS/hacking/crt0.S @@ -0,0 +1,6 @@ +@ this startup runtime is needed for 2 reasons: +@ 1/ gcc assumes the stack is already setup +@ 2/ gcc reorders functions when using -Os, so the first instruction might not be in _start + +mov sp, #0x30000 @ 64kB stack +b _start diff --git a/utils/AMS/hacking/find-offset.c b/utils/AMS/hacking/find-offset.c new file mode 100644 index 0000000..5c6ff02 --- /dev/null +++ b/utils/AMS/hacking/find-offset.c @@ -0,0 +1,119 @@ +/* find-offset.c - a tool to find the biggset block filled with the same + 32 bits word in a SansaV2 firmware + + Copyright (C) 2008 Rafaël Carré + +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; either version 2 of the License, or +(at your option) any later version. + +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., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA +*/ + +#include +#include +#include +#include +#include +#include +#include + +/* find the biggest area containing the same word */ + +int main(int argc, const char **argv) +{ + if(argc != 2) + { + fprintf(stderr, "Usage: %s \n", *argv); + return 1; + } + + struct stat st; + if( stat(argv[1], &st) == -1 ) + { + perror(argv[1]); + return 2; + } + + if(st.st_size <= 0x400 || st.st_size % 0x200) + { + fprintf(stderr, "%s doesn't look like a sansa firmware\n", argv[1]); + return 3; + } + + FILE *f = fopen(argv[1], "r"); + if(!f) + { + perror(argv[1]); + return 4; + } + + unsigned char header[0x400]; + if( fread(header, 0x400, 1, f) != 1 ) + { + perror(argv[1]); + fclose(f); + return 5; + } + + int firmware_size = ((header[9] << 8) | header[8]) * 0x200; + uint32_t *buf = malloc(firmware_size); + if(!buf) + { + fprintf(stderr,"%zd is too big\n", firmware_size); + fclose(f); + return 6; + } + + if(fread(buf, firmware_size, 1, f) != 1) + { + perror(argv[1]); + fclose(f); + free(buf); + return 7; + } + fclose(f); + + uint32_t offset, offset_max; + uint32_t val; /* word tracked */ + int n = 0; /* number of values found */ + int n_max = 0; /* highest block size found */ + int w = 0, words = firmware_size/4; + + while(w < words) + { + if(n == 0) + { + offset = w * 4; + val = buf[w]; /* start tracking another word */ + } + + if(buf[w] == val) /* increase the counter */ + n++; + else /* reset */ + { + if(n > n_max) /* store score? */ + { + n_max = n; + offset_max = offset; + } + n = 0; + } + + w++; /* next word */ + } + + printf("%d\n",offset_max); + + free(buf); + return 0; +} + diff --git a/utils/AMS/hacking/mkamsboot.c b/utils/AMS/hacking/mkamsboot.c index 30ca66e..481e41e 100644 --- a/utils/AMS/hacking/mkamsboot.c +++ b/utils/AMS/hacking/mkamsboot.c @@ -4,6 +4,7 @@ mkamsboot.c - a tool for merging bootloader code into an Sansa V2 (AMS) firmware file Copyright (C) Dave Chapman 2008 +Copyright (C) Rafaël Carré 2008 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 @@ -32,20 +33,38 @@ The first instruction in an AMS firmware file is always of the form: where [pc, #xxx] contains the entry point of the firmware - e.g. 0x00000138 -mkamsboot appends the Rockbox bootloader to the end of the original -firmware block in the firmware file and shifts the remaining contents of the firmware file to make space for it. +mkamsboot appends the 1st stage of the Rockbox bootloader to the end of the +original firmware block, while verifying it will not overflow the last 0x200 +bytes block. It also replaces the contents of [pc, #xxx] with the entry point of -our bootloader - i.e. the length of the original firmware block plus 4 +our bootloader - i.e. the length of the original firmware block plus 16 bytes. It then stores the original entry point from [pc, #xxx] in the first -four bytes of the Rockbox bootloader image, which is used by the -bootloader to dual-boot. +four bytes of the 1st stage Rockbox bootloader, which is used by the bootloader +to dual-boot. + +A separate tool is run on the file to find the longest block padded with the +same 32bits value, and is used to link the 2nd stage bootloader with the entry +point being this offset. + +mkamsboot takes this offset in argument and will write the starting offset, the +length of this block, and the 32bits value it is filled with, in the 12 +following bytes of the 1st stage. +The 1st stage use these values to restore the OF in its original form before +booting the OF. + +Next step is writing the 2nd stage bootloader at this offset, while verifying +that it is small enough to fit in. Finally, mkamsboot corrects the length and checksum in the main firmware headers (both copies), creating a new legal firmware file -which can be installed on the device. +which can be installed on the device, and which implements a recovery mode in +software in case the 2nd stage is gone bad. + +WARNING : pay special attention to modifications to the 1st stage, because the +software recovery mode could be broken, and you may end up bricking your device. */ @@ -113,8 +132,8 @@ void usage(void) int main(int argc, char* argv[]) { - char *infile, *bootfile, *outfile; - int fdin, fdboot,fdout; + char *infile, *bootfile, *boot2file, *outfile; + int fdin, fdboot,fdboot2,fdout; off_t len; uint32_t n; unsigned char* buf; @@ -123,18 +142,22 @@ int main(int argc, char* argv[]) uint32_t firmware_size; uint32_t firmware_paddedsize; uint32_t bootloader_size; + uint32_t stage2_size; uint32_t new_paddedsize; uint32_t sum,filesum; uint32_t new_length; + uint32_t stage2_offset; uint32_t i; - if(argc != 4) { + if(argc != 6) { usage(); } infile = argv[1]; bootfile = argv[2]; - outfile = argv[3]; + boot2file = argv[3]; + stage2_offset = atoi(argv[4]); + outfile = argv[5]; /* Open the bootloader file */ fdboot = open(bootfile, O_RDONLY|O_BINARY); @@ -147,6 +170,16 @@ int main(int argc, char* argv[]) bootloader_size = filesize(fdboot); + /* Open the 2nd stage bootloader file */ + fdboot2 = open(boot2file, O_RDONLY|O_BINARY); + if (fdboot2 < 0) + { + fprintf(stderr,"[ERR] Could not open %s for reading\n",boot2file); + return 1; + } + + stage2_size = filesize(fdboot2); + /* Open the firmware file */ fdin = open(infile,O_RDONLY|O_BINARY); @@ -213,7 +246,7 @@ int main(int argc, char* argv[]) origoffset = (ldr&0xfff) + 8; printf("original firmware entry point: 0x%08x\n",get_uint32le(buf + 0x400 + origoffset)); - printf("New entry point: 0x%08x\n", firmware_size + 4); + printf("New entry point: 0x%08x\n", firmware_size + 16); #if 0 /* Replace the "Product: Express" string with "Rockbox" */ @@ -231,10 +264,46 @@ int main(int argc, char* argv[]) } #endif + /* + * STAGE 2 + */ + + if(stage2_offset >= firmware_size) + { + fprintf(stderr,"[ERR] Invalid stage2 offset\n"); + return 1; + } + + uint32_t stage2_max; + uint32_t stage2_val = get_uint32le(buf + 0x400 + stage2_offset); + for(stage2_max=0; stage2_max< firmware_size - stage2_offset; stage2_max+=4) + if(stage2_val != get_uint32le(buf + 0x400 + stage2_offset + stage2_max)) + break; + + if(stage2_size > stage2_max) + { + fprintf(stderr,"[ERR] stage2 too big by %d words\n", + (stage2_size-stage2_max)/4); + return 1; + } + + /* read stage2 */ + unsigned char stage2[stage2_size]; + n = read(fdboot2, stage2, stage2_size); + if (n != stage2_size) { + fprintf(stderr,"[ERR] Could not read stage2 file\n"); + return 1; + } + close(fdboot2); + /* and copy it */ + memcpy(buf+0x400+stage2_offset,stage2,stage2_size); + + printf("Stage2 entry point: 0x%08x\n", stage2_offset); + n = read(fdboot, buf + 0x400 + firmware_size, bootloader_size); if (n != bootloader_size) { - fprintf(stderr,"[ERR] Could not bootloader file\n"); + fprintf(stderr,"[ERR] Could not read stage2 file\n"); return 1; } close(fdboot); @@ -242,9 +311,13 @@ int main(int argc, char* argv[]) /* Replace first word of the bootloader with the original entry point */ put_uint32le(buf + 0x400 + firmware_size, get_uint32le(buf + 0x400 + origoffset)); -#if 1 - put_uint32le(buf + 0x400 + origoffset, firmware_size + 4); -#endif + put_uint32le(buf + 0x400 + origoffset, firmware_size + 16); + + /* Replace 3 next words of the bootloader with stage2 offset, size, and + * value */ + put_uint32le(buf + 0x400 + firmware_size + 4, stage2_offset); + put_uint32le(buf + 0x400 + firmware_size + 8, stage2_max); + put_uint32le(buf + 0x400 + firmware_size + 12,stage2_val); /* Update checksum */ sum = calc_checksum(buf + 0x400,firmware_size + bootloader_size); diff --git a/utils/AMS/hacking/stage1.S b/utils/AMS/hacking/stage1.S new file mode 100644 index 0000000..ab1c8de --- /dev/null +++ b/utils/AMS/hacking/stage1.S @@ -0,0 +1,70 @@ +/* soc */ +.equ GPIOA, 0xC80B0000 +.equ CGU_PERI, 0xC80F0014 +.equ CCU_BASE, 0xC8100000 + +/* DO NOT MODIFY AT THE RISK TO BREAK THE SOFTWARE RECOVERY MODE */ +/* WARNING : THE GPIO MAPPING CORRESPONDS TO CLIP */ + +originalentry: +.word 0 /* This value is filled in by mkamsboot */ + +/* 2nd stage, filled by mkamsboot */ +stage2_offset: +.word 0 +stage2_size: +.word 0 +stage2_val: +.word 0 + + /* enable gpio clock */ + ldr r1, =CGU_PERI + ldr r2, [r1] + mov r0, #0x10000 /* gpio : bit 16 */ + orr r2, r2, r0 + str r2, [r1] + + /* copy the OF from ROM into RAM */ + mov r0, #0x20000 /* OF size */ + mov r1, #0x81000000 /* RAM */ + mov r2, #0x0 /* ROM */ + +loop_load: + subs r0, r0, #4 /* word copy */ + ldr r3, [r2, r0] + str r3, [r1, r0] + bne loop_load + /* XXX : DO NOT TOUCH r0 WE ASSUME IT IS #0 */ + + ldr r1, =CCU_BASE + mov r2, #1 /* ram */ + str r2, [r1, #8] /* ccu_memmap */ + + /* now the RAM is mapped at 0x0 and we continue execution in RAM */ + + /* check hold button / usb connection */ + ldr r1, =GPIOA + strb r0, [r1, #0x400] /* gpioa_dir (r0 == 0 after loop_load) */ + ldrb r0, [r1, #0x120] /* pin 3 : hold, 6 : usb */ + /* on E200 A3 is USB, and A6 is nothing */ + + cmp r0, #0 + bne resume /* resume OF if set */ + + adr lr, resume /* stage2 can use bx lr to resume OF */ + ldr pc, stage2_offset /* branch to stage2 */ + + /* back from stage 2 */ +resume: + ldr r0, stage2_offset + ldr r1, stage2_size + ldr r2, stage2_val + + /* restore OF state */ +clean_loop: + subs r1, r1, #4 /* word copy */ + str r2, [r0, r1] + bne clean_loop + + /* branch to OF */ + ldr pc, originalentry diff --git a/utils/AMS/hacking/stage2.c b/utils/AMS/hacking/stage2.c new file mode 100644 index 0000000..4a75146 --- /dev/null +++ b/utils/AMS/hacking/stage2.c @@ -0,0 +1,31 @@ +#include "stage2.h" + +void _start(void) +{ + /* if down button (clip) is pushed, branch to OF */ + GPIOB_DIR |= PIN(0); + if(GPIOB_PIN0) + goto resume; + + blink(); + +resume: + return; +} + +static void usleep(int i) { while(i--) ; } +static void msleep(int i) { while(i--) usleep(1024); } + +static void blink(void) +{ + GPIOD_DIR |= PIN(7); /* pin 7 as output */ + + int led = 0x80; /* bit 7 high */ + int i; + for(i=0; i<8; i++) + { + GPIOD_PIN7 = led; + led = ~led; /* toggle on/off */ + msleep(128); + } +} diff --git a/utils/AMS/hacking/stage2.h b/utils/AMS/hacking/stage2.h new file mode 100644 index 0000000..875dcf1 --- /dev/null +++ b/utils/AMS/hacking/stage2.h @@ -0,0 +1,15 @@ +#define ADDR *(volatile unsigned long *) + +#define GPIOB (ADDR 0xC80C0000) +#define GPIOB_DIR (ADDR 0xC80C0400) +#define GPIOB_PIN0 (ADDR 0xC80C0004) + +#define GPIOD (ADDR 0xC80E0000) +#define GPIOD_DIR (ADDR 0xC80E0400) +#define GPIOD_PIN7 (ADDR 0xC80E0200) + +#define PIN(x) (1 << x) + +static void usleep(int); +static void msleep(int); +static void blink(void); diff --git a/utils/AMS/hacking/test.S b/utils/AMS/hacking/test.S index 52f54bd..e69de29 100644 --- a/utils/AMS/hacking/test.S +++ b/utils/AMS/hacking/test.S @@ -1,11 +0,0 @@ - -/* This value is filled in by mkamsboot */ -originalentry: .word 0 - - /* A delay loop - just to prove we're running */ - mov r1, #0x500000 /* Approximately 5 seconds */ -loop: subs r1, r1, #1 - bne loop - - /* Now branch back to the original firmware's entry point */ - ldr pc, originalentry