diff options
-rw-r--r-- | rbutil/mkamsboot/Makefile | 98 | ||||
-rw-r--r-- | rbutil/mkamsboot/README | 8 | ||||
-rw-r--r-- | rbutil/mkamsboot/bin2c.c | 134 | ||||
-rw-r--r-- | rbutil/mkamsboot/dualboot.S | 126 | ||||
-rw-r--r-- | rbutil/mkamsboot/mkamsboot.c | 429 | ||||
-rw-r--r-- | rbutil/mkamsboot/test.S | 55 |
6 files changed, 613 insertions, 237 deletions
diff --git a/rbutil/mkamsboot/Makefile b/rbutil/mkamsboot/Makefile index 4040c716b5..e3d98f03f0 100644 --- a/rbutil/mkamsboot/Makefile +++ b/rbutil/mkamsboot/Makefile | |||
@@ -1,56 +1,94 @@ | |||
1 | # Change INFILE to point to your original firmware file | ||
2 | INFILE=$(HOME)/FW/AMS/CLIP/m300a-1.1.17A.bin | ||
3 | 1 | ||
4 | # OUTFILE is the file you copy to your device's root and rename to | 2 | # We use the UCL code available in the Rockbox tools/ directory |
5 | # (e.g.) m300a.bin | ||
6 | OUTFILE=patched.bin | ||
7 | 3 | ||
8 | # The uclpack command | 4 | CFLAGS=-I../../tools/ucl/include |
9 | UCLPACK=../../tools/uclpack | 5 | LIBUCL=../../tools/ucl/src/libucl.a |
10 | 6 | ||
11 | all: $(OUTFILE) | 7 | # Edit the following variables (plus copy/paste another set of rules) when |
8 | # adding a new target. mkamsboot.c also needs to be edited to refer to these | ||
9 | # new images. | ||
10 | # | ||
11 | # If anyone reading this wants to improve this makefile, please do! | ||
12 | 12 | ||
13 | mkamsboot: mkamsboot.c | 13 | BOOTIMAGES = bootimg_clip.o bootimg_e200v2.o |
14 | gcc -o mkamsboot -W -Wall mkamsboot.c | 14 | BOOTHEADERS = bootimg_clip.h bootimg_e200v2.h |
15 | 15 | ||
16 | extract_fw: extract_fw.c | 16 | CLIPFILES = dualboot-clip.o dualboot-clip.elf dualboot-clip.o \ |
17 | gcc -o extract_fw -W -Wall extract_fw.c | 17 | dualboot-clip.bin bootimg_clip.c bootimg_clip.h |
18 | |||
19 | E200V2FILES = dualboot-e200v2.o dualboot-e200v2.elf dualboot-e200v2.o \ | ||
20 | dualboot-e200v2.bin bootimg_e200v2.c bootimg_e200v2.h | ||
21 | |||
22 | all: mkamsboot | ||
23 | |||
24 | $(LIBUCL): | ||
25 | make -C ../../tools/ucl/src libucl.a | ||
26 | |||
27 | mkamsboot.o: mkamsboot.c $(BOOTHEADERS) uclimg.h | ||
28 | gcc $(CFLAGS) -c -o mkamsboot.o -W -Wall mkamsboot.c | ||
29 | |||
30 | mkamsboot: mkamsboot.o $(BOOTIMAGES) uclimg.o $(LIBUCL) | ||
31 | gcc -o mkamsboot mkamsboot.o $(BOOTIMAGES) uclimg.o $(LIBUCL) | ||
18 | 32 | ||
19 | # Rules for our test ARM application - assemble, link, then extract | 33 | # Rules for our test ARM application - assemble, link, then extract |
20 | # the binary code | 34 | # the binary code |
21 | 35 | ||
22 | test.o: test.S | 36 | # CLIP |
23 | arm-elf-as -o test.o test.S | 37 | |
38 | dualboot-clip.o: dualboot.S | ||
39 | arm-elf-gcc -DSANSA_CLIP -c -o dualboot-clip.o dualboot.S | ||
40 | |||
41 | dualboot-clip.elf: dualboot-clip.o | ||
42 | arm-elf-ld -e 0 -Ttext=0 -o dualboot-clip.elf dualboot-clip.o | ||
43 | |||
44 | dualboot-clip.bin: dualboot-clip.elf | ||
45 | arm-elf-objcopy -O binary dualboot-clip.elf dualboot-clip.bin | ||
46 | |||
47 | bootimg_clip.c bootimg_clip.h: dualboot-clip.bin bin2c | ||
48 | ./bin2c dualboot-clip.bin bootimg_clip | ||
49 | |||
50 | bootimg_clip.o: bootimg_clip.c | ||
51 | gcc -c -o bootimg_clip.o bootimg_clip.c | ||
52 | |||
53 | # E200V2 | ||
54 | |||
55 | dualboot-e200v2.o: dualboot.S | ||
56 | arm-elf-gcc -DSANSA_E200V2 -c -o dualboot-e200v2.o dualboot.S | ||
57 | |||
58 | dualboot-e200v2.elf: dualboot-e200v2.o | ||
59 | arm-elf-ld -e 0 -Ttext=0 -o dualboot-e200v2.elf dualboot-e200v2.o | ||
60 | |||
61 | dualboot-e200v2.bin: dualboot-e200v2.elf | ||
62 | arm-elf-objcopy -O binary dualboot-e200v2.elf dualboot-e200v2.bin | ||
24 | 63 | ||
25 | test.elf: test.o | 64 | bootimg_e200v2.c bootimg_e200v2.h: dualboot-e200v2.bin bin2c |
26 | arm-elf-ld -e 0 -Ttext=0 -o test.elf test.o | 65 | ./bin2c dualboot-e200v2.bin bootimg_e200v2 |
27 | 66 | ||
28 | test.bin: test.elf | 67 | bootimg_e200v2.o: bootimg_e200v2.c |
29 | arm-elf-objcopy -O binary test.elf test.bin | 68 | gcc -c -o bootimg_e200v2.o bootimg_e200v2.c |
30 | 69 | ||
31 | # Rules for the ucl unpack function - this is inserted in the padding at | 70 | # Rules for the ucl unpack function |
32 | # the end of the original firmware block | ||
33 | nrv2e_d8.o: nrv2e_d8.S | 71 | nrv2e_d8.o: nrv2e_d8.S |
34 | arm-elf-gcc -DPURE_THUMB -c -o nrv2e_d8.o nrv2e_d8.S | 72 | arm-elf-gcc -DPURE_THUMB -c -o nrv2e_d8.o nrv2e_d8.S |
35 | 73 | ||
36 | # NOTE: this function has no absolute references, so the link address (-e) | 74 | # NOTE: this function has no absolute references, so the link address (-e) |
37 | # is irrelevant. We just link at address 0. | 75 | # is irrelevant. We just link at address 0, but it can run from anywhere. |
38 | nrv2e_d8.elf: nrv2e_d8.o | 76 | nrv2e_d8.elf: nrv2e_d8.o |
39 | arm-elf-ld -e 0 -Ttext=0 -o nrv2e_d8.elf nrv2e_d8.o | 77 | arm-elf-ld -e 0 -Ttext=0 -o nrv2e_d8.elf nrv2e_d8.o |
40 | 78 | ||
41 | nrv2e_d8.bin: nrv2e_d8.elf | 79 | nrv2e_d8.bin: nrv2e_d8.elf |
42 | arm-elf-objcopy -O binary nrv2e_d8.elf nrv2e_d8.bin | 80 | arm-elf-objcopy -O binary nrv2e_d8.elf nrv2e_d8.bin |
43 | 81 | ||
44 | firmware_block.ucl: firmware_block.bin | 82 | uclimg.c uclimg.h: nrv2e_d8.bin bin2c |
45 | $(UCLPACK) --best --2e firmware_block.bin firmware_block.ucl | 83 | ./bin2c nrv2e_d8.bin uclimg |
46 | 84 | ||
47 | firmware_block.bin: $(INFILE) extract_fw | 85 | uclimg.o: uclimg.c |
48 | ./extract_fw $(INFILE) firmware_block.bin | 86 | gcc -c -o uclimg.o uclimg.c |
49 | 87 | ||
50 | $(OUTFILE): mkamsboot firmware_block.ucl test.bin nrv2e_d8.bin $(INFILE) | 88 | bin2c: bin2c.c |
51 | ./mkamsboot $(INFILE) firmware_block.ucl test.bin nrv2e_d8.bin $(OUTFILE) | 89 | gcc -o bin2c bin2c.c |
52 | 90 | ||
53 | clean: | 91 | clean: |
54 | rm -fr amsinfo mkamsboot test.o test.elf test.bin extract_fw \ | 92 | rm -f mkamsboot mkamsboot.o nrv2e_d8.o nrv2e_d8.elf nrv2e_d8.bin *~ \ |
55 | nrv2e_d8.o nrv2e_d8.elf nrv2e_d8.bin firmware_block.bin \ | 93 | bin2c uclimg.c uclimg.h uclimg.o \ |
56 | firmware_block.ucl $(OUTFILE) *~ | 94 | $(BOOTIMAGES) $(CLIPFILES) $(E200V2FILES) |
diff --git a/rbutil/mkamsboot/README b/rbutil/mkamsboot/README index bf9ea698a2..bd0d2a3eea 100644 --- a/rbutil/mkamsboot/README +++ b/rbutil/mkamsboot/README | |||
@@ -1,10 +1,6 @@ | |||
1 | mkamsboot | 1 | mkamsboot |
2 | --------- | 2 | --------- |
3 | 3 | ||
4 | A tool to inject some code (contained in test.S) into a firmware file. | 4 | A tool to inject a bootloader into a Sansa V2 (AMS) firmware file. |
5 | |||
6 | Edit the INFILE variable in the Makefile to point to the original | ||
7 | firmware file you want to patch, edit "test.S" appropriately, and then | ||
8 | type "make". | ||
9 | |||
10 | 5 | ||
6 | See comments in mkamsboot.c and dualboot.S for more information. | ||
diff --git a/rbutil/mkamsboot/bin2c.c b/rbutil/mkamsboot/bin2c.c new file mode 100644 index 0000000000..dce8013c31 --- /dev/null +++ b/rbutil/mkamsboot/bin2c.c | |||
@@ -0,0 +1,134 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2007 Dave Chapman | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include <stdio.h> | ||
23 | #include <string.h> | ||
24 | #include <sys/types.h> | ||
25 | #include <sys/stat.h> | ||
26 | #include <unistd.h> | ||
27 | #include <fcntl.h> | ||
28 | #include <stdlib.h> | ||
29 | |||
30 | #ifndef O_BINARY | ||
31 | #define O_BINARY 0 | ||
32 | #endif | ||
33 | |||
34 | static off_t filesize(int fd) | ||
35 | { | ||
36 | struct stat buf; | ||
37 | |||
38 | fstat(fd,&buf); | ||
39 | return buf.st_size; | ||
40 | } | ||
41 | |||
42 | static int write_cfile(const unsigned char* buf, off_t len, const char* cname) | ||
43 | { | ||
44 | char filename[256]; | ||
45 | FILE* fp; | ||
46 | int i; | ||
47 | |||
48 | snprintf(filename,256,"%s.c",cname); | ||
49 | |||
50 | fp = fopen(filename,"w+"); | ||
51 | if (fp == NULL) { | ||
52 | fprintf(stderr,"Couldn't open %s\n",filename); | ||
53 | return -1; | ||
54 | } | ||
55 | |||
56 | fprintf(fp,"/* Generated by bin2c */\n\n"); | ||
57 | fprintf(fp,"unsigned char %s[%d] = {",cname,len); | ||
58 | |||
59 | for (i=0;i<len;i++) { | ||
60 | if ((i % 16) == 0) { | ||
61 | fprintf(fp,"\n "); | ||
62 | } | ||
63 | if (i == (len-1)) { | ||
64 | fprintf(fp,"0x%02x",buf[i]); | ||
65 | } else { | ||
66 | fprintf(fp,"0x%02x, ",buf[i]); | ||
67 | } | ||
68 | } | ||
69 | fprintf(fp,"\n};\n"); | ||
70 | |||
71 | fclose(fp); | ||
72 | return 0; | ||
73 | } | ||
74 | |||
75 | static int write_hfile(off_t len, const char* cname) | ||
76 | { | ||
77 | char filename[256]; | ||
78 | FILE* fp; | ||
79 | |||
80 | snprintf(filename,256,"%s.h",cname); | ||
81 | fp = fopen(filename,"w+"); | ||
82 | if (fp == NULL) { | ||
83 | fprintf(stderr,"Couldn't open %s\n",filename); | ||
84 | return -1; | ||
85 | } | ||
86 | |||
87 | fprintf(fp,"/* Generated by bin2c */\n\n"); | ||
88 | fprintf(fp,"extern unsigned char %s[%d];\n",cname,len); | ||
89 | fclose(fp); | ||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | int main (int argc, char* argv[]) | ||
94 | { | ||
95 | char* infile; | ||
96 | char* cname; | ||
97 | int fd; | ||
98 | unsigned char* buf; | ||
99 | int len; | ||
100 | int n; | ||
101 | |||
102 | if (argc != 3) { | ||
103 | fprintf(stderr,"Usage: bin2c file cname\n"); | ||
104 | return 0; | ||
105 | } | ||
106 | |||
107 | infile=argv[1]; | ||
108 | cname=argv[2]; | ||
109 | |||
110 | fd = open(infile,O_RDONLY|O_BINARY); | ||
111 | if (fd < 0) { | ||
112 | fprintf(stderr,"Can not open %s\n",infile); | ||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | len = filesize(fd); | ||
117 | |||
118 | buf = malloc(len); | ||
119 | n = read(fd,buf,len); | ||
120 | if (n < len) { | ||
121 | fprintf(stderr,"Short read, aborting\n"); | ||
122 | return 0; | ||
123 | } | ||
124 | close(fd); | ||
125 | |||
126 | if (write_cfile(buf,len,cname) < 0) { | ||
127 | return -1; | ||
128 | } | ||
129 | if (write_hfile(len,cname) < 0) { | ||
130 | return -1; | ||
131 | } | ||
132 | |||
133 | return 0; | ||
134 | } | ||
diff --git a/rbutil/mkamsboot/dualboot.S b/rbutil/mkamsboot/dualboot.S new file mode 100644 index 0000000000..786f610aef --- /dev/null +++ b/rbutil/mkamsboot/dualboot.S | |||
@@ -0,0 +1,126 @@ | |||
1 | .text | ||
2 | |||
3 | /* This is the size of the Clip's RAM, but there is nothing to be gained | ||
4 | (at the moment) by making use of the larger RAM of other targets */ | ||
5 | |||
6 | .set DRAM_SIZE, 0x50000 | ||
7 | |||
8 | .set GPIOA, 0xC80B0000 | ||
9 | .set GPIOB, 0xC80C0000 | ||
10 | .set GPIOC, 0xC80D0000 | ||
11 | .set GPIOD, 0xC80E0000 | ||
12 | .set CGU_PERI, 0xC80F0014 | ||
13 | |||
14 | |||
15 | /* Vectors */ | ||
16 | ldr pc, =start | ||
17 | .word 0 | ||
18 | .word 0 | ||
19 | .word 0 | ||
20 | .word 0 | ||
21 | .word 0 | ||
22 | .word 0 | ||
23 | .word 0 | ||
24 | |||
25 | /* These values are filled in by mkamsboot - don't move them from offset 0x20 */ | ||
26 | uclunpack_end: .word 0 /* End of the ucl_unpack function */ | ||
27 | uclunpack_size: .word 0 /* Size in bytes of the ucl_unpack function */ | ||
28 | |||
29 | ucl_of_end: .word 0 /* End of the ucl-compressed OF image */ | ||
30 | ucl_of_size: .word 0 /* Size in bytes of the compressed OF image */ | ||
31 | |||
32 | ucl_rb_end: .word 0 /* End of the ucl-compressed RB image */ | ||
33 | ucl_rb_size: .word 0 /* Size in bytes of the compressed RB image */ | ||
34 | |||
35 | |||
36 | start: | ||
37 | /* First copy the UCL unpack function to the end of RAM */ | ||
38 | ldr r0, uclunpack_end /* Source */ | ||
39 | ldr r1, uclunpack_size /* Source length */ | ||
40 | sub r2, r0, r1 /* Source start - 1*/ | ||
41 | |||
42 | ldr r3, =DRAM_SIZE /* Destination end */ | ||
43 | |||
44 | uclcopy: | ||
45 | ldrb r4, [r0], #-1 | ||
46 | strb r4, [r3], #-1 | ||
47 | cmp r2, r3 | ||
48 | bne uclcopy | ||
49 | |||
50 | add r5, r2, #2 /* r5 is entry point of copy of uclunpack */ | ||
51 | /* function, plus one (for thumb mode */ | ||
52 | |||
53 | /* enable gpio clock */ | ||
54 | ldr r0, =CGU_PERI | ||
55 | ldr r1, [r0] | ||
56 | orr r1, r1, #(1<<16) | ||
57 | str r1, [r0] | ||
58 | |||
59 | /* we check A3 unconditionally of the model because it seems to be */ | ||
60 | /* either hold, either usb on every model */ | ||
61 | |||
62 | ldr r0, =GPIOA | ||
63 | mov r1, #0 | ||
64 | str r1, [r0, #0x400] | ||
65 | ldr r1, [r0, #0x20] /* read pin A3 */ | ||
66 | |||
67 | cmp r1, #0 | ||
68 | bne boot_of | ||
69 | |||
70 | /* here are model specific tests, for dual boot without a computer */ | ||
71 | |||
72 | #ifdef SANSA_CLIP | ||
73 | /* HOME button */ | ||
74 | ldr r0, =GPIOB | ||
75 | mov r1, #0 | ||
76 | str r1, [r0, #0x400] | ||
77 | ldr r1, [r0, #0x10] /* read pin B2 */ | ||
78 | |||
79 | cmp r1, #0 | ||
80 | bne boot_of | ||
81 | #elif defined(SANSA_E200V2) | ||
82 | /* DOWN button */ | ||
83 | ldr r0, =GPIOC | ||
84 | mov r1, #0 | ||
85 | str r1, [r0, #0x400] | ||
86 | ldr r1, [r0, #0x100] /* read pin C6 */ | ||
87 | |||
88 | cmp r1, #0 /* C6 = #0 means button pressed */ | ||
89 | beq boot_of | ||
90 | #else | ||
91 | #error No target-specific key check defined! | ||
92 | #endif | ||
93 | |||
94 | /* No button was held, so we boot rockbox */ | ||
95 | ldr r0, ucl_rb_end /* Address of compressed image */ | ||
96 | ldr r1, ucl_rb_size /* Compressed size */ | ||
97 | b decompress | ||
98 | |||
99 | boot_of: | ||
100 | ldr r0, ucl_of_end /* Address of compressed image */ | ||
101 | ldr r1, ucl_of_size /* Compressed size */ | ||
102 | |||
103 | |||
104 | decompress: | ||
105 | /* At this point: */ | ||
106 | /* r5 = entry point (plus one for thumb) of uclunpack function */ | ||
107 | /* r3 = destination_end for copy of UCL image */ | ||
108 | /* r0 = source_end for UCL image to copy */ | ||
109 | /* r1 = size of UCL image to copy */ | ||
110 | |||
111 | sub r4, r3, r1 /* r4 := destination_start - 1 */ | ||
112 | |||
113 | fw_copy: | ||
114 | ldrb r2, [r0], #-1 | ||
115 | strb r2, [r3], #-1 | ||
116 | cmp r3, r4 /* Stop when we reached dest_start-1 */ | ||
117 | bne fw_copy | ||
118 | |||
119 | /* Call the ucl decompress function, which will branch to 0x0 */ | ||
120 | /* on completion */ | ||
121 | add r0, r3, #1 /* r0 := Start of compressed image */ | ||
122 | /* r1 already contains compressed size */ | ||
123 | mov r2, #0 /* r2 := Destination for unpacking */ | ||
124 | bx r5 /* Branch to uclunpack, switching to thumb */ | ||
125 | |||
126 | /* never reached */ | ||
diff --git a/rbutil/mkamsboot/mkamsboot.c b/rbutil/mkamsboot/mkamsboot.c index 52ead58b69..e617b1204a 100644 --- a/rbutil/mkamsboot/mkamsboot.c +++ b/rbutil/mkamsboot/mkamsboot.c | |||
@@ -30,36 +30,46 @@ We replace the main firmware block (bytes 0x400..0x400+firmware_size) | |||
30 | as follows: | 30 | as follows: |
31 | 31 | ||
32 | 32 | ||
33 | --------------------- 0x0 | 33 | ---------------------- 0x0 |
34 | | | | 34 | | | |
35 | | Rockbox bootloader | | 35 | | Dual-boot code | |
36 | | | | 36 | | | |
37 | |---------------------| | 37 | |----------------------| |
38 | | EMPTY SPACE | | 38 | | EMPTY SPACE | |
39 | |---------------------| | 39 | |----------------------| |
40 | | ucl unpack function | | 40 | | | |
41 | |---------------------| | 41 | | compressed RB image | |
42 | | | | 42 | | | |
43 | | compressed OF image | | 43 | |----------------------| |
44 | | | | 44 | | | |
45 | | | | 45 | | compressed OF image | |
46 | --------------------- | 46 | | | |
47 | |----------------------| | ||
48 | | UCL unpack function | | ||
49 | ---------------------- | ||
47 | 50 | ||
48 | This entire block fits into the space previously occupied by the main | 51 | This entire block fits into the space previously occupied by the main |
49 | firmware block, and gives about 40KB of space to store the Rockbox | 52 | firmware block - the space saved by compressing the OF image is used |
50 | bootloader. This could be increased if we also UCL compress the | 53 | to store the compressed version of the Rockbox bootloader. The OF |
51 | Rockbox bootloader. | 54 | image is typically about 120KB, which allows us to store a Rockbox |
55 | bootloader with an uncompressed size of about 60KB-70KB. | ||
52 | 56 | ||
53 | mkamsboot then corrects the checksums and writes a new legal firmware | 57 | mkamsboot then corrects the checksums and writes a new legal firmware |
54 | file which can be installed on the device. | 58 | file which can be installed on the device. |
55 | 59 | ||
56 | Our bootloader first checks for the "dual-boot" keypress, and then either: | 60 | When the Sansa device boots, this firmware block is loaded to RAM at |
61 | address 0x0 and executed. | ||
57 | 62 | ||
58 | a) Copies the ucl unpack function and compressed OF image to an unused | 63 | Firstly, the dual-boot code will copy the UCL unpack function to the |
59 | part of RAM and then branches to the ucl_unpack function, which | 64 | end of RAM. |
60 | will then branch to 0x0 after decompressing the OF to that location. | 65 | |
66 | Then, depending on the detection of the dual-boot keypress, either the | ||
67 | OF image or the Rockbox image is copied to the end of RAM (just before | ||
68 | the ucl unpack function) and uncompress it to the start of RAM. | ||
69 | |||
70 | Finally, the ucl unpack function branches to address 0x0, passing | ||
71 | execution to the uncompressed firmware. | ||
61 | 72 | ||
62 | b) Continues running with our test code | ||
63 | 73 | ||
64 | */ | 74 | */ |
65 | 75 | ||
@@ -73,14 +83,62 @@ b) Continues running with our test code | |||
73 | #include <unistd.h> | 83 | #include <unistd.h> |
74 | #include <string.h> | 84 | #include <string.h> |
75 | 85 | ||
86 | #include <ucl/ucl.h> | ||
87 | |||
88 | /* Headers for ARM code binaries */ | ||
89 | #include "uclimg.h" | ||
90 | #include "bootimg_clip.h" | ||
91 | #include "bootimg_e200v2.h" | ||
76 | 92 | ||
77 | /* Win32 compatibility */ | 93 | /* Win32 compatibility */ |
78 | #ifndef O_BINARY | 94 | #ifndef O_BINARY |
79 | #define O_BINARY 0 | 95 | #define O_BINARY 0 |
80 | #endif | 96 | #endif |
81 | 97 | ||
98 | #ifndef VERSION | ||
99 | #define VERSION "0.1" | ||
100 | #endif | ||
82 | 101 | ||
83 | #define PAD_TO_BOUNDARY(x) (((x) + 0x1ff) & ~0x1ff) | 102 | enum |
103 | { | ||
104 | MODEL_UNKNOWN = -1, | ||
105 | MODEL_FUZE = 0, | ||
106 | MODEL_CLIP, | ||
107 | MODEL_CLIPV2, | ||
108 | MODEL_E200, | ||
109 | MODEL_M200, | ||
110 | MODEL_C200 | ||
111 | }; | ||
112 | |||
113 | static const char* model_names[] = | ||
114 | { | ||
115 | "Fuze", | ||
116 | "Clip", | ||
117 | "Clip V2", | ||
118 | "E200", | ||
119 | "M200", | ||
120 | "C200" | ||
121 | }; | ||
122 | |||
123 | static const unsigned char* bootloaders[] = | ||
124 | { | ||
125 | NULL, | ||
126 | bootimg_clip, | ||
127 | NULL, | ||
128 | bootimg_e200v2, | ||
129 | NULL, | ||
130 | NULL | ||
131 | }; | ||
132 | |||
133 | static const int bootloader_sizes[] = | ||
134 | { | ||
135 | 0, | ||
136 | sizeof(bootimg_clip), | ||
137 | 0, | ||
138 | sizeof(bootimg_e200v2), | ||
139 | 0, | ||
140 | 0 | ||
141 | }; | ||
84 | 142 | ||
85 | 143 | ||
86 | /* This magic should appear at the start of any UCL file */ | 144 | /* This magic should appear at the start of any UCL file */ |
@@ -105,12 +163,6 @@ static uint32_t get_uint32le(unsigned char* p) | |||
105 | return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); | 163 | return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); |
106 | } | 164 | } |
107 | 165 | ||
108 | static uint32_t get_uint32be(unsigned char* p) | ||
109 | { | ||
110 | return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; | ||
111 | |||
112 | } | ||
113 | |||
114 | static void put_uint32le(unsigned char* p, uint32_t x) | 166 | static void put_uint32le(unsigned char* p, uint32_t x) |
115 | { | 167 | { |
116 | p[0] = x & 0xff; | 168 | p[0] = x & 0xff; |
@@ -130,186 +182,271 @@ static int calc_checksum(unsigned char* buf, uint32_t n) | |||
130 | return sum; | 182 | return sum; |
131 | } | 183 | } |
132 | 184 | ||
133 | void usage(void) | 185 | static int get_model(int model_id) |
134 | { | 186 | { |
135 | printf("Usage: mkamsboot <firmware file> <ucl image> <boot file> <ucl unpack file> <output file>\n"); | 187 | switch(model_id) |
188 | { | ||
189 | case 0x1e: | ||
190 | return MODEL_FUZE; | ||
191 | case 0x22: | ||
192 | return MODEL_CLIP; | ||
193 | case 0x23: | ||
194 | return MODEL_C200; | ||
195 | case 0x24: | ||
196 | return MODEL_E200; | ||
197 | case 0x25: | ||
198 | return MODEL_M200; | ||
199 | case 0x27: | ||
200 | return MODEL_CLIPV2; | ||
201 | } | ||
136 | 202 | ||
137 | exit(1); | 203 | return MODEL_UNKNOWN; |
138 | } | 204 | } |
139 | 205 | ||
140 | int main(int argc, char* argv[]) | 206 | |
207 | static unsigned char* uclpack(unsigned char* inbuf, int insize, int* outsize) | ||
141 | { | 208 | { |
142 | char *infile, *uclfile, *bootfile, *uclunpackfile, *outfile; | 209 | int maxsize; |
143 | int fdin, fducl, fdboot, fduclunpack, fdout; | 210 | unsigned char* outbuf; |
144 | off_t len; | 211 | int r; |
145 | unsigned char uclheader[26]; | ||
146 | uint32_t n; | ||
147 | unsigned char* buf; | ||
148 | uint32_t firmware_size; | ||
149 | uint32_t firmware_paddedsize; | ||
150 | uint32_t bootloader_size; | ||
151 | uint32_t ucl_size; | ||
152 | uint32_t ucl_paddedsize; | ||
153 | uint32_t uclunpack_size; | ||
154 | uint32_t sum,filesum; | ||
155 | uint32_t i; | ||
156 | 212 | ||
157 | if(argc != 6) { | 213 | /* The following formula comes from the UCL documentation */ |
158 | usage(); | 214 | maxsize = insize + (insize / 8) + 256; |
159 | } | ||
160 | 215 | ||
161 | infile = argv[1]; | 216 | /* Allocate some memory for the output buffer */ |
162 | uclfile = argv[2]; | 217 | outbuf = malloc(maxsize); |
163 | bootfile = argv[3]; | 218 | |
164 | uclunpackfile = argv[4]; | 219 | if (outbuf == NULL) { |
165 | outfile = argv[5]; | 220 | return NULL; |
166 | 221 | } | |
167 | /* Open the bootloader file */ | 222 | |
168 | fdboot = open(bootfile, O_RDONLY|O_BINARY); | 223 | r = ucl_nrv2e_99_compress( |
169 | if (fdboot < 0) | 224 | (const ucl_bytep) inbuf, |
225 | (ucl_uint) insize, | ||
226 | (ucl_bytep) outbuf, | ||
227 | (ucl_uintp) outsize, | ||
228 | 0, 10, NULL, NULL); | ||
229 | |||
230 | if (r != UCL_E_OK || *outsize > maxsize) | ||
170 | { | 231 | { |
171 | fprintf(stderr,"[ERR] Could not open %s for reading\n",bootfile); | 232 | /* this should NEVER happen, and implies memory corruption */ |
172 | return 1; | 233 | fprintf(stderr, "internal error - compression failed: %d\n", r); |
234 | free(outbuf); | ||
235 | return NULL; | ||
173 | } | 236 | } |
174 | 237 | ||
175 | bootloader_size = filesize(fdboot); | 238 | return outbuf; |
239 | } | ||
176 | 240 | ||
241 | static unsigned char* load_file(char* filename, off_t* bufsize) | ||
242 | { | ||
243 | int fd; | ||
244 | unsigned char* buf; | ||
245 | off_t n; | ||
177 | 246 | ||
178 | /* Open the UCL-compressed image of the firmware block */ | 247 | fd = open(filename, O_RDONLY|O_BINARY); |
179 | fduclunpack = open(uclunpackfile, O_RDONLY|O_BINARY); | 248 | if (fd < 0) |
180 | if (fduclunpack < 0) | ||
181 | { | 249 | { |
182 | fprintf(stderr,"[ERR] Could not open %s for reading\n",uclunpackfile); | 250 | fprintf(stderr,"[ERR] Could not open %s for reading\n",filename); |
183 | return 1; | 251 | return NULL; |
184 | } | 252 | } |
185 | 253 | ||
186 | uclunpack_size = filesize(fduclunpack); | 254 | *bufsize = filesize(fd); |
187 | 255 | ||
188 | 256 | buf = malloc(*bufsize); | |
189 | /* Open the UCL-compressed image of the firmware block */ | 257 | if (buf == NULL) { |
190 | fducl = open(uclfile, O_RDONLY|O_BINARY); | 258 | fprintf(stderr,"[ERR] Could not allocate memory for %s\n",filename); |
191 | if (fducl < 0) | 259 | return NULL; |
192 | { | ||
193 | fprintf(stderr,"[ERR] Could not open %s for reading\n",uclfile); | ||
194 | return 1; | ||
195 | } | 260 | } |
196 | 261 | ||
197 | /* Some UCL file sanity checks */ | 262 | n = read(fd, buf, *bufsize); |
198 | n = read(fducl, uclheader, sizeof(uclheader)); | ||
199 | 263 | ||
200 | if (n != sizeof(uclheader)) { | 264 | if (n != *bufsize) { |
201 | fprintf(stderr,"[ERR] Could not read header from UCL file\n"); | 265 | fprintf(stderr,"[ERR] Could not read file %s\n",filename); |
202 | return 1; | 266 | return NULL; |
203 | } | 267 | } |
204 | 268 | ||
205 | if (memcmp(uclmagic, uclheader, sizeof(uclmagic))!=0) { | 269 | return buf; |
206 | fprintf(stderr,"[ERR] Invalid UCL file\n"); | 270 | } |
271 | |||
272 | |||
273 | int main(int argc, char* argv[]) | ||
274 | { | ||
275 | char *infile, *bootfile, *outfile; | ||
276 | int fdout; | ||
277 | off_t len; | ||
278 | uint32_t n; | ||
279 | unsigned char* buf; | ||
280 | int firmware_size; | ||
281 | off_t bootloader_size; | ||
282 | uint32_t ucl_size; | ||
283 | uint32_t ucl_paddedsize; | ||
284 | uint32_t sum,filesum; | ||
285 | uint8_t model_id; | ||
286 | int model; | ||
287 | uint32_t i; | ||
288 | unsigned char* of_packed; | ||
289 | int of_packedsize; | ||
290 | unsigned char* rb_unpacked; | ||
291 | unsigned char* rb_packed; | ||
292 | int rb_packedsize; | ||
293 | int fw_version; | ||
294 | int totalsize; | ||
295 | unsigned char* p; | ||
296 | |||
297 | fprintf(stderr,"mkamsboot v" VERSION " - (C) Dave Chapman 2008\n"); | ||
298 | fprintf(stderr,"This is free software; see the source for copying conditions. There is NO\n"); | ||
299 | fprintf(stderr,"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"); | ||
300 | |||
301 | if(argc != 4) { | ||
302 | printf("Usage: mkamsboot <firmware file> <boot file> <output file>\n\n"); | ||
207 | return 1; | 303 | return 1; |
208 | } | 304 | } |
209 | 305 | ||
210 | if (uclheader[12] != 0x2e) { | 306 | infile = argv[1]; |
211 | fprintf(stderr,"[ERR] Unsupported UCL compression format (0x%02x) - only 0x2e supported.\n",uclheader[12]); | 307 | bootfile = argv[2]; |
308 | outfile = argv[3]; | ||
309 | |||
310 | /* Load bootloader file */ | ||
311 | rb_unpacked = load_file(bootfile, &bootloader_size); | ||
312 | if (rb_unpacked == NULL) { | ||
313 | fprintf(stderr,"[ERR] Could not load %s\n",bootfile); | ||
212 | return 1; | 314 | return 1; |
213 | } | 315 | } |
214 | ucl_size = get_uint32be(&uclheader[22]) + 8; | ||
215 | ucl_paddedsize = (ucl_size + 3) & ~0x3; | ||
216 | 316 | ||
217 | if (ucl_size + 26 > (unsigned)filesize(fducl)) { | 317 | /* Load original firmware file */ |
218 | fprintf(stderr, "[ERR] Size mismatch in UCL file\n"); | 318 | buf = load_file(infile, &len); |
319 | |||
320 | if (buf == NULL) { | ||
321 | fprintf(stderr,"[ERR] Could not load bootloader file\n"); | ||
219 | return 1; | 322 | return 1; |
220 | } | 323 | } |
221 | 324 | ||
222 | /* Open the firmware file */ | 325 | /* TODO: Do some more sanity checks on the OF image - e.g. checksum */ |
223 | fdin = open(infile,O_RDONLY|O_BINARY); | ||
224 | 326 | ||
225 | if (fdin < 0) { | 327 | if (get_uint32le(&buf[0x204])==0x0000f000) { |
226 | fprintf(stderr,"[ERR] Could not open %s for reading\n",infile); | 328 | fw_version = 2; |
227 | return 1; | 329 | model_id = buf[0x219]; |
330 | } else { | ||
331 | fw_version = 1; | ||
332 | model_id = buf[0x215]; | ||
228 | } | 333 | } |
229 | 334 | ||
230 | if ((len = filesize(fdin)) < 0) | 335 | model = get_model(model_id); |
231 | return 1; | ||
232 | 336 | ||
233 | /* Allocate memory for the OF image - we don't change the size */ | 337 | if (model == MODEL_UNKNOWN) { |
234 | if ((buf = malloc(len)) == NULL) { | 338 | fprintf(stderr,"[ERR] Unknown firmware - model id 0x%02x\n",model_id); |
235 | fprintf(stderr,"[ERR] Could not allocate buffer for input file (%d bytes)\n",(int)len); | ||
236 | return 1; | 339 | return 1; |
237 | } | 340 | } |
238 | 341 | ||
239 | n = read(fdin, buf, len); | 342 | if (bootloaders[model] == NULL) { |
240 | 343 | fprintf(stderr,"[ERR] Unsupported model - \"%s\"\n",model_names[model]); | |
241 | if (n != (uint32_t)len) { | 344 | free(buf); |
242 | fprintf(stderr,"[ERR] Could not read firmware file\n"); | 345 | free(rb_unpacked); |
243 | return 1; | 346 | return 1; |
244 | } | 347 | } |
245 | 348 | ||
246 | close(fdin); | 349 | printf("[INFO] Patching %s firmware\n",model_names[model]); |
247 | 350 | ||
248 | /* Get the firmware size */ | 351 | /* Get the firmware size */ |
249 | firmware_size = get_uint32le(&buf[0x0c]); | 352 | firmware_size = get_uint32le(&buf[0x0c]); |
250 | 353 | ||
251 | /* Round size up to next multiple of 0x200 */ | 354 | /* Compress the original firmware image */ |
355 | of_packed = uclpack(buf + 0x400, firmware_size, &of_packedsize); | ||
356 | if (of_packed == NULL) { | ||
357 | fprintf(stderr,"[ERR] Could not compress original firmware\n"); | ||
358 | free(buf); | ||
359 | free(rb_unpacked); | ||
360 | return 1; | ||
361 | } | ||
362 | |||
363 | rb_packed = uclpack(rb_unpacked, bootloader_size, &rb_packedsize); | ||
364 | if (rb_packed == NULL) { | ||
365 | fprintf(stderr,"[ERR] Could not compress %s\n",bootfile); | ||
366 | free(buf); | ||
367 | free(rb_unpacked); | ||
368 | free(of_packed); | ||
369 | return 1; | ||
370 | } | ||
252 | 371 | ||
253 | firmware_paddedsize = PAD_TO_BOUNDARY(firmware_size); | 372 | /* We are finished with the unpacked version of the bootloader */ |
373 | free(rb_unpacked); | ||
254 | 374 | ||
255 | fprintf(stderr,"Original firmware size - %d bytes\n",firmware_size); | 375 | fprintf(stderr,"[INFO] Original firmware size: %d bytes\n",firmware_size); |
256 | fprintf(stderr,"Padded firmware size - %d bytes\n",firmware_paddedsize); | 376 | fprintf(stderr,"[INFO] Packed OF size: %d bytes\n",of_packedsize); |
257 | fprintf(stderr,"Bootloader size - %d bytes\n",bootloader_size); | 377 | fprintf(stderr,"[INFO] Bootloader size: %d bytes\n",(int)bootloader_size); |
258 | fprintf(stderr,"UCL image size - %d bytes (%d bytes padded)\n",ucl_size,ucl_paddedsize); | 378 | fprintf(stderr,"[INFO] Packed bootloader size: %d bytes\n",rb_packedsize); |
259 | fprintf(stderr,"UCL unpack function size - %d bytes\n",uclunpack_size); | 379 | fprintf(stderr,"[INFO] Dual-boot function size: %d bytes\n",bootloader_sizes[model]); |
260 | fprintf(stderr,"Original total size of firmware - %d bytes\n",(int)len); | 380 | fprintf(stderr,"[INFO] UCL unpack function size: %d bytes\n",sizeof(uclimg)); |
261 | 381 | ||
262 | /* Check we have room for our bootloader - in the future, we could UCL | 382 | totalsize = bootloader_sizes[model] + sizeof(uclimg) + of_packedsize + |
263 | pack this image as well if we need to. */ | 383 | rb_packedsize; |
264 | if (bootloader_size > (firmware_size - ucl_paddedsize - uclunpack_size)) { | 384 | |
265 | fprintf(stderr,"[ERR] Bootloader too large (%d bytes, %d available)\n", | 385 | fprintf(stderr,"[INFO] Total size of new image: %d bytes\n",totalsize); |
266 | bootloader_size, firmware_size - ucl_paddedsize - uclunpack_size); | 386 | |
387 | if (totalsize > firmware_size) { | ||
388 | fprintf(stderr,"[ERR] No room to insert bootloader, aborting\n"); | ||
389 | free(buf); | ||
390 | free(rb_unpacked); | ||
391 | free(of_packed); | ||
267 | return 1; | 392 | return 1; |
268 | } | 393 | } |
269 | 394 | ||
270 | /* Zero the original firmware area - not needed, but helps debugging */ | 395 | /* Zero the original firmware area - not needed, but helps debugging */ |
271 | memset(buf + 0x400, 0, firmware_size); | 396 | memset(buf + 0x400, 0, firmware_size); |
272 | 397 | ||
273 | /* Locate our bootloader code at the start of the firmware block */ | ||
274 | n = read(fdboot, buf + 0x400, bootloader_size); | ||
275 | 398 | ||
276 | if (n != bootloader_size) { | 399 | /* Insert dual-boot bootloader at offset 0 */ |
277 | fprintf(stderr,"[ERR] Could not load bootloader file\n"); | 400 | memcpy(buf + 0x400, bootloaders[model], bootloader_sizes[model]); |
278 | return 1; | ||
279 | } | ||
280 | close(fdboot); | ||
281 | 401 | ||
282 | /* Locate the compressed image of the original firmware block at the end | 402 | /* We are filling the firmware buffer backwards from the end */ |
283 | of the firmware block */ | 403 | p = buf + 0x400 + firmware_size; |
284 | n = read(fducl, buf + 0x400 + firmware_size - ucl_paddedsize, ucl_size); | ||
285 | 404 | ||
286 | if (n != ucl_size) { | 405 | /* 1 - UCL unpack function */ |
287 | fprintf(stderr,"[ERR] Could not load ucl file\n"); | 406 | p -= sizeof(uclimg); |
288 | return 1; | 407 | memcpy(p, uclimg, sizeof(uclimg)); |
289 | } | ||
290 | close(fducl); | ||
291 | 408 | ||
409 | /* 2 - Compressed copy of original firmware */ | ||
410 | p -= of_packedsize; | ||
411 | memcpy(p, of_packed, of_packedsize); | ||
292 | 412 | ||
293 | /* Locate our UCL unpack function before copy of the compressed firmware */ | 413 | /* 3 - Compressed copy of Rockbox bootloader */ |
294 | n = read(fduclunpack, buf + 0x400 + firmware_size - ucl_paddedsize - uclunpack_size, uclunpack_size); | 414 | p -= rb_packedsize; |
415 | memcpy(p, rb_packed, rb_packedsize); | ||
295 | 416 | ||
296 | if (n != uclunpack_size) { | 417 | /* Write the locations of the various images to the variables at the |
297 | fprintf(stderr,"[ERR] Could not load uclunpack file\n"); | 418 | start of the dualboot image - we save the location of the last byte |
298 | return 1; | 419 | in each image, along with the size in bytes */ |
299 | } | 420 | |
300 | close(fduclunpack); | 421 | /* UCL unpack function */ |
422 | put_uint32le(&buf[0x420], firmware_size); | ||
423 | put_uint32le(&buf[0x424], sizeof(uclimg)); | ||
424 | |||
425 | /* Compressed original firmware image */ | ||
426 | put_uint32le(&buf[0x428], firmware_size - sizeof(uclimg)); | ||
427 | put_uint32le(&buf[0x42c], of_packedsize); | ||
301 | 428 | ||
302 | put_uint32le(&buf[0x420], 0x40000 - ucl_paddedsize - uclunpack_size + 1); /* UCL unpack entry point */ | 429 | /* Compressed Rockbox image */ |
303 | put_uint32le(&buf[0x424], 0x40000 - ucl_paddedsize); /* Location of OF */ | 430 | put_uint32le(&buf[0x430], firmware_size - sizeof(uclimg) - of_packedsize); |
304 | put_uint32le(&buf[0x428], ucl_size); /* Size of UCL image */ | 431 | put_uint32le(&buf[0x434], rb_packedsize); |
305 | put_uint32le(&buf[0x42c], firmware_size - uclunpack_size - ucl_paddedsize); /* Start of data to copy */ | ||
306 | put_uint32le(&buf[0x430], uclunpack_size + ucl_paddedsize); /* Size of data to copy */ | ||
307 | 432 | ||
308 | /* Update checksum */ | 433 | |
434 | /* Update the firmware block checksum */ | ||
309 | sum = calc_checksum(buf + 0x400,firmware_size); | 435 | sum = calc_checksum(buf + 0x400,firmware_size); |
310 | 436 | ||
311 | put_uint32le(&buf[0x04], sum); | 437 | if (fw_version == 1) { |
312 | put_uint32le(&buf[0x204], sum); | 438 | put_uint32le(&buf[0x04], sum); |
439 | put_uint32le(&buf[0x204], sum); | ||
440 | } else { | ||
441 | /* TODO: Verify that this is correct for the v2 firmware */ | ||
442 | |||
443 | put_uint32le(&buf[0x08], sum); | ||
444 | put_uint32le(&buf[0x208], sum); | ||
445 | |||
446 | /* Update the header checksums */ | ||
447 | put_uint32le(&buf[0x1fc], calc_checksum(buf, 0x1fc)); | ||
448 | put_uint32le(&buf[0x3fc], calc_checksum(buf + 0x200, 0x1fc)); | ||
449 | } | ||
313 | 450 | ||
314 | /* Update the whole-file checksum */ | 451 | /* Update the whole-file checksum */ |
315 | filesum = 0; | 452 | filesum = 0; |
diff --git a/rbutil/mkamsboot/test.S b/rbutil/mkamsboot/test.S deleted file mode 100644 index a4757b44ce..0000000000 --- a/rbutil/mkamsboot/test.S +++ /dev/null | |||
@@ -1,55 +0,0 @@ | |||
1 | /* int ucl_nrv2e_decompress_8(const unsigned char *src, unsigned char *dst, | ||
2 | unsigned long *dst_len) */ | ||
3 | |||
4 | .text | ||
5 | .global ucl_nrv2e_decompress_8 | ||
6 | |||
7 | |||
8 | /* Vectors */ | ||
9 | ldr pc, =start | ||
10 | .word 0 | ||
11 | .word 0 | ||
12 | .word 0 | ||
13 | .word 0 | ||
14 | .word 0 | ||
15 | .word 0 | ||
16 | .word 0 | ||
17 | |||
18 | /* These values are filled in by mkamsboot - don't move them from offset 0x20 */ | ||
19 | ucl_unpack: .word 0 /* Entry point (plus 1 - for thumb) of ucl_unpack after copy*/ | ||
20 | ucl_start: .word 0 /* Start of the ucl-compressed OF image after copy */ | ||
21 | ucl_size: .word 0 /* Length in bytes of the compressed OF image */ | ||
22 | copy_start: .word 0 /* Start of the copy of the ucl_unpack function */ | ||
23 | copy_size: .word 0 /* uclunpack_size + ucl_paddedsize */ | ||
24 | |||
25 | start: | ||
26 | /* A delay loop - just to prove we're running */ | ||
27 | mov r1, #0x500000 /* Approximately 5 seconds */ | ||
28 | loop: subs r1, r1, #1 | ||
29 | bne loop | ||
30 | |||
31 | /* First copy the compressed firmware to unused RAM */ | ||
32 | |||
33 | ldr r0, copy_start /* Source */ | ||
34 | ldr r1, copy_size /* Source length */ | ||
35 | |||
36 | mov r2, #0x40000 /* Destination end */ | ||
37 | sub r2, r2, r1 | ||
38 | |||
39 | memcpy: | ||
40 | ldrb r3, [r0], #1 | ||
41 | strb r3, [r2], #1 | ||
42 | cmp r2, #0x40000 /* Stop when we reached dest_end */ | ||
43 | bne memcpy | ||
44 | |||
45 | /* Call the ucl decompress function, which will branch to 0x0 */ | ||
46 | /* on completion */ | ||
47 | |||
48 | ldr r0, ucl_start /* Address of compressed image */ | ||
49 | ldr r1, ucl_size /* Compressed size */ | ||
50 | mov r2, #0 /* Destination */ | ||
51 | |||
52 | ldr r3, ucl_unpack | ||
53 | bx r3 | ||
54 | |||
55 | /* never reached */ | ||