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
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
OUTFILE=patched.bin
# The uclpack command
UCLPACK=../../../tools/uclpack
all: amsinfo $(OUTFILE)
@ -15,6 +16,9 @@ amsinfo: amsinfo.c
mkamsboot: 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
# the binary code
@ -22,13 +26,34 @@ test.o: test.S
arm-elf-as -o test.o test.S
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
arm-elf-objcopy -O binary test.elf test.bin
$(OUTFILE): mkamsboot test.bin $(INFILE)
./mkamsboot $(INFILE) test.bin $(OUTFILE)
# Rules for the ucl unpack function - this is inserted in the padding at
# 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:
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.
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
firmware block in the firmware file and shifts the remaining contents of the firmware file to make space for it.
0x20 - Entry point (plus 1 - for thumb mode) of the ucl_unpack function
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
our bootloader - i.e. the length of the original firmware block plus 4
bytes.
mkamsboot then corrects the length (to include the UCL decompress
function) and checksum in the main firmware headers (both copies),
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
four bytes of the Rockbox bootloader image, which is used by the
bootloader to dual-boot.
Our bootloader first checks for the "dual-boot" keypress, and then either:
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.
a) Branches to the ucl_unpack function, which will then branch to 0x0 after
decompressing the OF.
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)
{
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);
}
int main(int argc, char* argv[])
{
char *infile, *bootfile, *outfile;
int fdin, fdboot,fdout;
char *infile, *uclfile, *bootfile, *uclunpackfile, *outfile;
int fdin, fducl, fdboot, fduclunpack, fdout;
off_t len;
uint32_t n;
unsigned char* buf;
uint32_t ldr;
uint32_t origoffset;
uint32_t firmware_size;
uint32_t firmware_paddedsize;
uint32_t bootloader_size;
uint32_t new_paddedsize;
uint32_t ucl_size;
uint32_t uclunpack_size;
uint32_t sum,filesum;
uint32_t new_length;
uint32_t i;
if(argc != 4) {
if(argc != 6) {
usage();
}
infile = argv[1];
bootfile = argv[2];
outfile = argv[3];
uclfile = argv[2];
bootfile = argv[3];
uclunpackfile = argv[4];
outfile = argv[5];
/* Open the bootloader file */
fdboot = open(bootfile, O_RDONLY|O_BINARY);
@ -147,6 +154,28 @@ int main(int argc, char* argv[])
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 */
fdin = open(infile,O_RDONLY|O_BINARY);
@ -158,9 +187,8 @@ int main(int argc, char* argv[])
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 + PAD_TO_BOUNDARY(bootloader_size))) == NULL) {
/* Allocate memory for the OF image - we don't change the size */
if ((buf = malloc(len)) == NULL) {
fprintf(stderr,"[ERR] Could not allocate buffer for input file (%d bytes)\n",(int)len);
return 1;
}
@ -181,91 +209,83 @@ int main(int argc, char* argv[])
firmware_paddedsize = PAD_TO_BOUNDARY(firmware_size);
/* Total new size */
new_paddedsize = PAD_TO_BOUNDARY(firmware_size + bootloader_size);
/* Total new size of firmware file */
new_length = len + (new_paddedsize - firmware_paddedsize);
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);
fprintf(stderr,"Original firmware size - %d bytes\n",firmware_size);
fprintf(stderr,"Padded firmware size - %d bytes\n",firmware_paddedsize);
fprintf(stderr,"Bootloader size - %d bytes\n",bootloader_size);
fprintf(stderr,"UCL image size - %d bytes\n",ucl_size);
fprintf(stderr,"UCL unpack function size - %d bytes\n",uclunpack_size);
fprintf(stderr,"Original total size of firmware - %d bytes\n",(int)len);
/* 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;
}
ldr = get_uint32le(&buf[0x400]);
if ((ldr & 0xfffff000) != 0xe59ff000) {
fprintf(stderr,"[ERR] Firmware file doesn't start with an \"ldr pc, [pc, #xx]\" instruction.\n");
/* Check we have enough room for the UCL unpack function. This
needs to be outside the firmware block, so if we wanted to
support every firmware version, we could store this function in
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;
}
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);
/* Zero the original firmware area - not needed, but helps debugging */
memset(buf + 0x400, 0, firmware_size);
#if 0
/* Replace the "Product: Express" string with "Rockbox" */
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);
/* Locate our bootloader code at the start of the firmware block */
n = read(fdboot, buf + 0x400, 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;
}
close(fdboot);
/* Replace first word of the bootloader with the original entry point */
put_uint32le(buf + 0x400 + firmware_size, get_uint32le(buf + 0x400 + origoffset));
/* Locate the compressed image of the original firmware block at the end
of the firmware block */
n = read(fducl, buf + 0x400 + firmware_size - ucl_size, ucl_size);
#if 1
put_uint32le(buf + 0x400 + origoffset, firmware_size + 4);
#endif
if (n != ucl_size) {
fprintf(stderr,"[ERR] Could not load ucl file\n");
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 */
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[0x204], sum);
/* Update firmware block count */
put_uint32le(&buf[0x08], new_paddedsize / 0x200);
put_uint32le(&buf[0x208], new_paddedsize / 0x200);
/* Update firmware size */
put_uint32le(&buf[0x0c], firmware_size + bootloader_size);
put_uint32le(&buf[0x20c], firmware_size + bootloader_size);
put_uint32le(&buf[0x0c], firmware_size + uclunpack_size);
put_uint32le(&buf[0x20c], firmware_size + uclunpack_size);
/* Update the whole-file checksum */
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]);
put_uint32le(buf + new_length - 4, filesum);
put_uint32le(buf + len - 4, filesum);
/* Write the new firmware */
@ -276,7 +296,12 @@ int main(int argc, char* argv[])
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);

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.
*/
adr r12,1+.thumb_nrv2e_d8; bx r12 @ enter THUMB mode
#endif
.code 16 @ THUMB mode
.thumb_func
#endif
.thumb_nrv2e_d8:
#if 0
push {r2,r3, r4,r5,r6,r7, lr}
#define sp_DST0 0 /* stack offset of original dst */
#endif
add srclim,len,src @ srclim= eof_src;
#if 1==SAFE /*{*/
ldr tmp,[r3] @ len_dst
@ -103,12 +105,17 @@ bad_src_n2e: # return value will be 1
add src,#1
#endif /*}*/
eof_n2e:
#if 0
pop {r3,r4} @ r3= orig_dst; r4= plen_dst
sub src,srclim @ 0 if actual src length equals expected length
sub dst,r3 @ actual dst length
str dst,[r4]
pop {r4,r5,r6,r7 /*,pc*/}
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]
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 */
originalentry: .word 0
.text
.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 */
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
/* Call the ucl decompress function, which will branch to 0x0
on completion */
ldr r0, ucl_start /* Source */
mov r1, #0 /* Destination */
ldr r2, ucl_unpack
bx r2