Untested (i.e. will almost certainly brick your device if you attempt to use it) first attempt at making mkamsboot store the original firmware as a UCL compressed image. If it works, then this means we have about 40KB (depending on target and OF version) for our bootloader code. I repeat: This is UNTESTED and needs reviewing fully before attempting to install on a device.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18675 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Dave Chapman 2008-10-01 09:15:44 +00:00
parent fe72d57996
commit 757f5112e2
5 changed files with 305 additions and 96 deletions

View file

@ -1,4 +1,3 @@
# Change INFILE to point to your original firmware file # Change INFILE to point to your original firmware file
INFILE=$(HOME)/FW/AMS/CLIP/m300a-1.1.17A.bin INFILE=$(HOME)/FW/AMS/CLIP/m300a-1.1.17A.bin
@ -6,6 +5,8 @@ INFILE=$(HOME)/FW/AMS/CLIP/m300a-1.1.17A.bin
# (e.g.) m300a.bin # (e.g.) m300a.bin
OUTFILE=patched.bin OUTFILE=patched.bin
# The uclpack command
UCLPACK=../../../tools/uclpack
all: amsinfo $(OUTFILE) all: amsinfo $(OUTFILE)
@ -15,6 +16,9 @@ amsinfo: amsinfo.c
mkamsboot: mkamsboot.c mkamsboot: mkamsboot.c
gcc -o mkamsboot -W -Wall mkamsboot.c gcc -o mkamsboot -W -Wall mkamsboot.c
extract_fw: extract_fw.c
gcc -o extract_fw -W -Wall extract_fw.c
# Rules for our test ARM application - assemble, link, then extract # Rules for our test ARM application - assemble, link, then extract
# the binary code # the binary code
@ -22,13 +26,34 @@ test.o: test.S
arm-elf-as -o test.o test.S arm-elf-as -o test.o test.S
test.elf: test.o test.elf: test.o
arm-elf-ld -e 0 -o test.elf test.o arm-elf-ld -e 0 -Ttext=0 -o test.elf test.o
test.bin: test.elf test.bin: test.elf
arm-elf-objcopy -O binary test.elf test.bin arm-elf-objcopy -O binary test.elf test.bin
$(OUTFILE): mkamsboot test.bin $(INFILE) # Rules for the ucl unpack function - this is inserted in the padding at
./mkamsboot $(INFILE) test.bin $(OUTFILE) # the end of the original firmware block
nrv2e_d8.o: nrv2e_d8.S
arm-elf-gcc -DPURE_THUMB -c -o nrv2e_d8.o nrv2e_d8.S
# NOTE: this function has no absolute references, so the link address (-e)
# is irrelevant. We just link at address 0.
nrv2e_d8.elf: nrv2e_d8.o
arm-elf-ld -e 0 -Ttext=0 -o nrv2e_d8.elf nrv2e_d8.o
nrv2e_d8.bin: nrv2e_d8.elf
arm-elf-objcopy -O binary nrv2e_d8.elf nrv2e_d8.bin
firmware_block.ucl: firmware_block.bin
$(UCLPACK) --best --2e firmware_block.bin firmware_block.ucl
firmware_block.bin: $(INFILE) extract_fw
./extract_fw $(INFILE) firmware_block.bin
$(OUTFILE): mkamsboot firmware_block.ucl test.bin nrv2e_d8.bin $(INFILE)
./mkamsboot $(INFILE) firmware_block.ucl test.bin nrv2e_d8.bin $(OUTFILE)
clean: clean:
rm -fr amsinfo mkamsboot test.bin test.o test.elf $(OUTFILE) *~ rm -fr amsinfo mkamsboot test.o test.elf test.bin extract_fw \
nrv2e_d8.o nrv2e_d8.elf nrv2e_d8.bin firmware_block.bin \
firmware_block.ucl $(OUTFILE) *~

View file

@ -0,0 +1,129 @@
/*
extract_fw.c - extract the main firmware image from a Sansa V2 (AMS) firmware
file
Copyright (C) Dave Chapman 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
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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
/* Win32 compatibility */
#ifndef O_BINARY
#define O_BINARY 0
#endif
static off_t filesize(int fd) {
struct stat buf;
if (fstat(fd,&buf) < 0) {
perror("[ERR] Checking filesize of input file");
return -1;
} else {
return(buf.st_size);
}
}
static uint32_t get_uint32le(unsigned char* p)
{
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}
void usage(void)
{
printf("Usage: extract_fw <firmware file> <output file>\n");
exit(1);
}
int main(int argc, char* argv[])
{
char *infile, *outfile;
int fdin, fdout;
off_t len;
uint32_t n;
unsigned char* buf;
uint32_t firmware_size;
if(argc != 3) {
usage();
}
infile = argv[1];
outfile = argv[2];
/* Open the firmware file */
fdin = open(infile,O_RDONLY|O_BINARY);
if (fdin < 0) {
fprintf(stderr,"[ERR] Could not open %s for reading\n",infile);
return 1;
}
if ((len = filesize(fdin)) < 0)
return 1;
/* We will need no more memory than the total size plus the bootloader size
padded to a boundary */
if ((buf = malloc(len)) == NULL) {
fprintf(stderr,"[ERR] Could not allocate buffer for input file (%d bytes)\n",(int)len);
return 1;
}
n = read(fdin, buf, len);
if (n != (uint32_t)len) {
fprintf(stderr,"[ERR] Could not read firmware file\n");
return 1;
}
close(fdin);
/* Get the firmware size */
firmware_size = get_uint32le(&buf[0x0c]);
fdout = open(outfile, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666);
if (fdout < 0) {
fprintf(stderr,"[ERR] Could not open %s for writing\n",outfile);
return 1;
}
n = write(fdout, buf + 0x400, firmware_size);
if (n != (uint32_t)firmware_size) {
fprintf(stderr,"[ERR] Could not write firmware block\n");
return 1;
}
/* Clean up */
close(fdout);
free(buf);
return 0;
}

View file

@ -26,26 +26,33 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
Insert a Rockbox bootloader into an AMS original firmware file. Insert a Rockbox bootloader into an AMS original firmware file.
The first instruction in an AMS firmware file is always of the form: We replace the main firmware block (bytes 0x400..padded_firmware_size+0x400)
with the following:
ldr pc, [pc, #xxx] Bytes 0..(firmware_size-ucl_size) - Our bootloader code
Bytes (firmware_size-ucl_size)..firmware_size - UCL compressed OF image
Bytes firmware_size..padded_firmware_size - UCL decompress function
where [pc, #xxx] contains the entry point of the firmware - e.g. 0x00000138 mkamsboot writes the following values at offsets into our bootloader code:
mkamsboot appends the Rockbox bootloader to the end of the original 0x20 - Entry point (plus 1 - for thumb mode) of the ucl_unpack function
firmware block in the firmware file and shifts the remaining contents of the firmware file to make space for it. 0x24 - Location of the UCL compressed version of the original firmware block
It also replaces the contents of [pc, #xxx] with the entry point of mkamsboot then corrects the length (to include the UCL decompress
our bootloader - i.e. the length of the original firmware block plus 4 function) and checksum in the main firmware headers (both copies),
bytes. creating a new legal firmware file which can be installed on the
device.
It then stores the original entry point from [pc, #xxx] in the first Our bootloader first checks for the "dual-boot" keypress, and then either:
four bytes of the Rockbox bootloader image, which is used by the
bootloader to dual-boot.
Finally, mkamsboot corrects the length and checksum in the main a) Branches to the ucl_unpack function, which will then branch to 0x0 after
firmware headers (both copies), creating a new legal firmware file decompressing the OF.
which can be installed on the device.
b) Continues running with our test code
This method uses no RAM outside the padded area of the original
firmware block - the UCL compression can happen in-place when the
compressed image is stored at the end of the destination buffer.
*/ */
@ -106,35 +113,35 @@ static int calc_checksum(unsigned char* buf, uint32_t n)
void usage(void) void usage(void)
{ {
printf("Usage: mkamsboot <firmware file> <boot file> <output file>\n"); printf("Usage: mkamsboot <firmware file> <ucl image> <boot file> <ucl unpack file> <output file>\n");
exit(1); exit(1);
} }
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
char *infile, *bootfile, *outfile; char *infile, *uclfile, *bootfile, *uclunpackfile, *outfile;
int fdin, fdboot,fdout; int fdin, fducl, fdboot, fduclunpack, fdout;
off_t len; off_t len;
uint32_t n; uint32_t n;
unsigned char* buf; unsigned char* buf;
uint32_t ldr;
uint32_t origoffset;
uint32_t firmware_size; uint32_t firmware_size;
uint32_t firmware_paddedsize; uint32_t firmware_paddedsize;
uint32_t bootloader_size; uint32_t bootloader_size;
uint32_t new_paddedsize; uint32_t ucl_size;
uint32_t uclunpack_size;
uint32_t sum,filesum; uint32_t sum,filesum;
uint32_t new_length;
uint32_t i; uint32_t i;
if(argc != 4) { if(argc != 6) {
usage(); usage();
} }
infile = argv[1]; infile = argv[1];
bootfile = argv[2]; uclfile = argv[2];
outfile = argv[3]; bootfile = argv[3];
uclunpackfile = argv[4];
outfile = argv[5];
/* Open the bootloader file */ /* Open the bootloader file */
fdboot = open(bootfile, O_RDONLY|O_BINARY); fdboot = open(bootfile, O_RDONLY|O_BINARY);
@ -147,6 +154,28 @@ int main(int argc, char* argv[])
bootloader_size = filesize(fdboot); bootloader_size = filesize(fdboot);
/* Open the UCL-compressed image of the firmware block */
fduclunpack = open(uclunpackfile, O_RDONLY|O_BINARY);
if (fduclunpack < 0)
{
fprintf(stderr,"[ERR] Could not open %s for reading\n",uclunpackfile);
return 1;
}
uclunpack_size = filesize(fduclunpack);
/* Open the UCL-compressed image of the firmware block */
fducl = open(uclfile, O_RDONLY|O_BINARY);
if (fducl < 0)
{
fprintf(stderr,"[ERR] Could not open %s for reading\n",uclfile);
return 1;
}
ucl_size = filesize(fducl);
/* Open the firmware file */ /* Open the firmware file */
fdin = open(infile,O_RDONLY|O_BINARY); fdin = open(infile,O_RDONLY|O_BINARY);
@ -158,9 +187,8 @@ int main(int argc, char* argv[])
if ((len = filesize(fdin)) < 0) if ((len = filesize(fdin)) < 0)
return 1; return 1;
/* We will need no more memory than the total size plus the bootloader size /* Allocate memory for the OF image - we don't change the size */
padded to a boundary */ if ((buf = malloc(len)) == NULL) {
if ((buf = malloc(len + PAD_TO_BOUNDARY(bootloader_size))) == NULL) {
fprintf(stderr,"[ERR] Could not allocate buffer for input file (%d bytes)\n",(int)len); fprintf(stderr,"[ERR] Could not allocate buffer for input file (%d bytes)\n",(int)len);
return 1; return 1;
} }
@ -181,91 +209,83 @@ int main(int argc, char* argv[])
firmware_paddedsize = PAD_TO_BOUNDARY(firmware_size); firmware_paddedsize = PAD_TO_BOUNDARY(firmware_size);
/* Total new size */ fprintf(stderr,"Original firmware size - %d bytes\n",firmware_size);
new_paddedsize = PAD_TO_BOUNDARY(firmware_size + bootloader_size); fprintf(stderr,"Padded firmware size - %d bytes\n",firmware_paddedsize);
fprintf(stderr,"Bootloader size - %d bytes\n",bootloader_size);
/* Total new size of firmware file */ fprintf(stderr,"UCL image size - %d bytes\n",ucl_size);
new_length = len + (new_paddedsize - firmware_paddedsize); fprintf(stderr,"UCL unpack function size - %d bytes\n",uclunpack_size);
fprintf(stderr,"Original total size of firmware - %d bytes\n",(int)len);
fprintf(stderr,"Original firmware size - 0x%08x\n",firmware_size);
fprintf(stderr,"Padded firmware size - 0x%08x\n",firmware_paddedsize);
fprintf(stderr,"Bootloader size - 0x%08x\n",bootloader_size);
fprintf(stderr,"New padded size - 0x%08x\n",new_paddedsize);
fprintf(stderr,"Original total size of firmware - 0x%08x\n",(int)len);
fprintf(stderr,"New total size of firmware - 0x%08x\n",new_length);
if (firmware_paddedsize != new_paddedsize) {
/* We don't know how to safely increase the firmware size, so abort */
fprintf(stderr,
"[ERR] Bootloader too large (%d bytes - %d bytes available), aborting.\n",
bootloader_size, firmware_paddedsize - firmware_size);
/* Check we have room for our bootloader - in the future, we could UCL
pack this image as well if we need to. */
if (bootloader_size > (firmware_size - ucl_size)) {
fprintf(stderr,"[ERR] Bootloader too large (%d bytes, %d available)\n",
bootloader_size, firmware_size - ucl_size);
return 1; return 1;
} }
ldr = get_uint32le(&buf[0x400]); /* Check we have enough room for the UCL unpack function. This
needs to be outside the firmware block, so if we wanted to
if ((ldr & 0xfffff000) != 0xe59ff000) { support every firmware version, we could store this function in
fprintf(stderr,"[ERR] Firmware file doesn't start with an \"ldr pc, [pc, #xx]\" instruction.\n"); the main firmware block, and then copy it to an unused part of
RAM. */
if (uclunpack_size > (firmware_paddedsize - firmware_size)) {
fprintf(stderr,"[ERR] UCL unpack function too large (%d bytes, %d available)\n",
uclunpack_size, firmware_paddedsize - firmware_size);
return 1; return 1;
} }
origoffset = (ldr&0xfff) + 8;
printf("original firmware entry point: 0x%08x\n",get_uint32le(buf + 0x400 + origoffset)); /* Zero the original firmware area - not needed, but helps debugging */
printf("New entry point: 0x%08x\n", firmware_size + 4); memset(buf + 0x400, 0, firmware_size);
#if 0 /* Locate our bootloader code at the start of the firmware block */
/* Replace the "Product: Express" string with "Rockbox" */ n = read(fdboot, buf + 0x400, bootloader_size);
i = 0x400 + firmware_size - 7;
while ((i > 0x400) && (memcmp(&buf[i],"Express",7)!=0))
i--;
i = (i + 3) & ~0x3;
if (i >= 0x400) {
printf("Replacing \"Express\" string at offset 0x%08x\n",i);
memcpy(&buf[i],"Rockbox",7);
} else {
printf("Could not find \"Express\" string to replace\n");
}
#endif
n = read(fdboot, buf + 0x400 + firmware_size, bootloader_size);
if (n != bootloader_size) { if (n != bootloader_size) {
fprintf(stderr,"[ERR] Could not bootloader file\n"); fprintf(stderr,"[ERR] Could not load bootloader file\n");
return 1; return 1;
} }
close(fdboot); close(fdboot);
/* Replace first word of the bootloader with the original entry point */ /* Locate the compressed image of the original firmware block at the end
put_uint32le(buf + 0x400 + firmware_size, get_uint32le(buf + 0x400 + origoffset)); of the firmware block */
n = read(fducl, buf + 0x400 + firmware_size - ucl_size, ucl_size);
#if 1 if (n != ucl_size) {
put_uint32le(buf + 0x400 + origoffset, firmware_size + 4); fprintf(stderr,"[ERR] Could not load ucl file\n");
#endif return 1;
}
close(fducl);
/* Locate our UCL unpack function in the padding after the firmware block */
n = read(fduclunpack, buf + 0x400 + firmware_size, uclunpack_size);
if (n != uclunpack_size) {
fprintf(stderr,"[ERR] Could not load uclunpack file\n");
return 1;
}
close(fduclunpack);
put_uint32le(&buf[0x420], firmware_size + 1); /* UCL unpack entry point */
put_uint32le(&buf[0x424], firmware_size - ucl_size); /* Location of OF */
/* Update checksum */ /* Update checksum */
sum = calc_checksum(buf + 0x400,firmware_size + bootloader_size); sum = calc_checksum(buf + 0x400,firmware_size + uclunpack_size);
put_uint32le(&buf[0x04], sum); put_uint32le(&buf[0x04], sum);
put_uint32le(&buf[0x204], sum); put_uint32le(&buf[0x204], sum);
/* Update firmware block count */
put_uint32le(&buf[0x08], new_paddedsize / 0x200);
put_uint32le(&buf[0x208], new_paddedsize / 0x200);
/* Update firmware size */ /* Update firmware size */
put_uint32le(&buf[0x0c], firmware_size + bootloader_size); put_uint32le(&buf[0x0c], firmware_size + uclunpack_size);
put_uint32le(&buf[0x20c], firmware_size + bootloader_size); put_uint32le(&buf[0x20c], firmware_size + uclunpack_size);
/* Update the whole-file checksum */ /* Update the whole-file checksum */
filesum = 0; filesum = 0;
for (i=0;i < new_length - 4; i+=4) for (i=0;i < (unsigned)len - 4; i+=4)
filesum += get_uint32le(&buf[i]); filesum += get_uint32le(&buf[i]);
put_uint32le(buf + new_length - 4, filesum); put_uint32le(buf + len - 4, filesum);
/* Write the new firmware */ /* Write the new firmware */
@ -276,7 +296,12 @@ int main(int argc, char* argv[])
return 1; return 1;
} }
write(fdout, buf, new_length); n = write(fdout, buf, len);
if (n != (unsigned)len) {
fprintf(stderr,"[ERR] Could not write firmware file\n");
return 1;
}
close(fdout); close(fdout);

View file

@ -77,13 +77,15 @@ ucl_nrv2e_decompress_8: .globl ucl_nrv2e_decompress_8 @ ARM mode
For SAFE mode: at call, *plen_dst must be allowed length of output buffer. For SAFE mode: at call, *plen_dst must be allowed length of output buffer.
*/ */
adr r12,1+.thumb_nrv2e_d8; bx r12 @ enter THUMB mode adr r12,1+.thumb_nrv2e_d8; bx r12 @ enter THUMB mode
#endif
.code 16 @ THUMB mode .code 16 @ THUMB mode
.thumb_func .thumb_func
#endif
.thumb_nrv2e_d8: .thumb_nrv2e_d8:
#if 0
push {r2,r3, r4,r5,r6,r7, lr} push {r2,r3, r4,r5,r6,r7, lr}
#define sp_DST0 0 /* stack offset of original dst */ #define sp_DST0 0 /* stack offset of original dst */
#endif
add srclim,len,src @ srclim= eof_src; add srclim,len,src @ srclim= eof_src;
#if 1==SAFE /*{*/ #if 1==SAFE /*{*/
ldr tmp,[r3] @ len_dst ldr tmp,[r3] @ len_dst
@ -103,12 +105,17 @@ bad_src_n2e: # return value will be 1
add src,#1 add src,#1
#endif /*}*/ #endif /*}*/
eof_n2e: eof_n2e:
#if 0
pop {r3,r4} @ r3= orig_dst; r4= plen_dst pop {r3,r4} @ r3= orig_dst; r4= plen_dst
sub src,srclim @ 0 if actual src length equals expected length sub src,srclim @ 0 if actual src length equals expected length
sub dst,r3 @ actual dst length sub dst,r3 @ actual dst length
str dst,[r4] str dst,[r4]
pop {r4,r5,r6,r7 /*,pc*/} pop {r4,r5,r6,r7 /*,pc*/}
pop {r1}; bx r1 @ "pop {,pc}" fails return to ARM mode on ARMv4T pop {r1}; bx r1 @ "pop {,pc}" fails return to ARM mode on ARMv4T
#else
mov r0, #0
bx r0 /* Branch to 0x0, switch to ARM mode */
#endif
get1_n2e: @ In: Carry set [from adding 0x80000000 (1<<31) to itself] get1_n2e: @ In: Carry set [from adding 0x80000000 (1<<31) to itself]
ldrb bits,[src] @ zero-extend next byte ldrb bits,[src] @ zero-extend next byte

View file

@ -1,11 +1,34 @@
/* int ucl_nrv2e_decompress_8(const unsigned char *src, unsigned char *dst,
unsigned long *dst_len) */
/* This value is filled in by mkamsboot */ .text
originalentry: .word 0 .global ucl_nrv2e_decompress_8
/* Vectors */
ldr pc, =start
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
/* These values are filled in by mkamsboot - don't move them from offset 0x20 */
ucl_unpack: .word 0 /* Entry point (plus 1 - for thumb) of ucl_unpack */
ucl_start: .word 0 /* Start of the ucl-compressed OF image */
start:
/* A delay loop - just to prove we're running */ /* A delay loop - just to prove we're running */
mov r1, #0x500000 /* Approximately 5 seconds */ mov r1, #0x500000 /* Approximately 5 seconds */
loop: subs r1, r1, #1 loop: subs r1, r1, #1
bne loop bne loop
/* Now branch back to the original firmware's entry point */ /* Call the ucl decompress function, which will branch to 0x0
ldr pc, originalentry on completion */
ldr r0, ucl_start /* Source */
mov r1, #0 /* Destination */
ldr r2, ucl_unpack
bx r2