diff options
author | Aidan MacDonald <amachronic@protonmail.com> | 2021-05-11 13:25:26 +0100 |
---|---|---|
committer | Aidan MacDonald <amachronic@protonmail.com> | 2021-05-12 10:35:20 +0000 |
commit | cc22df198d0ccb64dfdfe0c2f247f7d86b7fd750 (patch) | |
tree | e6cd8aaa33c161bc0f72a9b35e763372b20f910d /rbutil/jztool/src/fiiom3k.c | |
parent | 3748117ee3623437d63ba2016637fd532dbdd4b9 (diff) | |
download | rockbox-cc22df198d0ccb64dfdfe0c2f247f7d86b7fd750.tar.gz rockbox-cc22df198d0ccb64dfdfe0c2f247f7d86b7fd750.zip |
jztool: Support new M3K bootloader
Change-Id: Ia2d96893a9a5c77deb71c1fe32ae5a0585093f5b
Diffstat (limited to 'rbutil/jztool/src/fiiom3k.c')
-rw-r--r-- | rbutil/jztool/src/fiiom3k.c | 373 |
1 files changed, 182 insertions, 191 deletions
diff --git a/rbutil/jztool/src/fiiom3k.c b/rbutil/jztool/src/fiiom3k.c index a43863c2f7..72e25a1220 100644 --- a/rbutil/jztool/src/fiiom3k.c +++ b/rbutil/jztool/src/fiiom3k.c | |||
@@ -20,264 +20,255 @@ | |||
20 | ****************************************************************************/ | 20 | ****************************************************************************/ |
21 | 21 | ||
22 | #include "jztool.h" | 22 | #include "jztool.h" |
23 | #include "jztool_private.h" | ||
24 | #include "microtar.h" | ||
25 | #include "ucl/ucl.h" | ||
26 | #include <stdbool.h> | ||
23 | #include <string.h> | 27 | #include <string.h> |
24 | 28 | ||
25 | #define IMAGE_ADDR 0 | 29 | static uint32_t xread32(const uint8_t* d) |
26 | #define IMAGE_SIZE (128 * 1024) | ||
27 | #define SPL_OFFSET 0 | ||
28 | #define SPL_SIZE (12 * 1024) | ||
29 | #define BOOT_OFFSET (26 * 1024) | ||
30 | #define BOOT_SIZE (102 * 1024) | ||
31 | |||
32 | int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr) | ||
33 | { | 30 | { |
34 | jz_buffer* buf = jz_buffer_alloc(IMAGE_SIZE, NULL); | 31 | uint32_t r = 0; |
35 | if(!buf) | 32 | r |= d[0] << 24; |
36 | return JZ_ERR_OUT_OF_MEMORY; | 33 | r |= d[1] << 16; |
37 | 34 | r |= d[2] << 8; | |
38 | int rc = jz_x1000_read_flash(dev, IMAGE_ADDR, buf->size, buf->data); | 35 | r |= d[3] << 0; |
39 | if(rc < 0) { | 36 | return r; |
40 | jz_buffer_free(buf); | ||
41 | return rc; | ||
42 | } | ||
43 | |||
44 | *bufptr = buf; | ||
45 | return JZ_SUCCESS; | ||
46 | } | 37 | } |
47 | 38 | ||
48 | int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf) | 39 | /* adapted from firmware/common/ucl_decompress.c */ |
40 | static jz_buffer* ucl_unpack(const uint8_t* src, uint32_t src_len, | ||
41 | uint32_t* dst_len) | ||
49 | { | 42 | { |
50 | int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size); | 43 | static const uint8_t magic[8] = |
51 | if(rc < 0 || image_size != IMAGE_SIZE) | 44 | {0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a}; |
52 | return JZ_ERR_BAD_FILE_FORMAT; | ||
53 | 45 | ||
54 | rc = jz_x1000_write_flash(dev, IMAGE_ADDR, image_size, image_buf); | 46 | jz_buffer* buffer = NULL; |
55 | if(rc < 0) | ||
56 | return rc; | ||
57 | 47 | ||
58 | return JZ_SUCCESS; | 48 | /* make sure there are enough bytes for the header */ |
59 | } | 49 | if(src_len < 18) |
50 | goto error; | ||
60 | 51 | ||
61 | int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size, | 52 | /* avoid memcmp for reasons of code size */ |
62 | const void* spl_buf, size_t spl_size, | 53 | for(size_t i = 0; i < sizeof(magic); ++i) |
63 | const void* boot_buf, size_t boot_size) | 54 | if(src[i] != magic[i]) |
64 | { | 55 | goto error; |
65 | int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size); | ||
66 | if(rc < 0) { | ||
67 | jz_log(jz, JZ_LOG_ERROR, "Boot image is invalid: %d", rc); | ||
68 | return JZ_ERR_BAD_FILE_FORMAT; | ||
69 | } | ||
70 | 56 | ||
71 | rc = jz_identify_x1000_spl(spl_buf, spl_size); | 57 | /* read the other header fields */ |
72 | if(rc < 0) { | 58 | /* uint32_t flags = xread32(&src[8]); */ |
73 | jz_log(jz, JZ_LOG_ERROR, "SPL image is invalid: %d", rc); | 59 | uint8_t method = src[12]; |
74 | return JZ_ERR_BAD_FILE_FORMAT; | 60 | /* uint8_t level = src[13]; */ |
75 | } | 61 | uint32_t block_size = xread32(&src[14]); |
76 | 62 | ||
77 | if(spl_size > SPL_SIZE) { | 63 | /* check supported compression method */ |
78 | jz_log(jz, JZ_LOG_ERROR, "SPL is too big"); | 64 | if(method != 0x2e) |
79 | return JZ_ERR_BAD_FILE_FORMAT; | 65 | goto error; |
80 | } | ||
81 | 66 | ||
82 | rc = jz_identify_scramble_image(boot_buf, boot_size); | 67 | /* validate */ |
83 | if(rc < 0) { | 68 | if(block_size < 1024 || block_size > 8*1024*1024) |
84 | jz_log(jz, JZ_LOG_ERROR, "Bootloader image is invalid: %d", rc); | 69 | goto error; |
85 | return JZ_ERR_BAD_FILE_FORMAT; | ||
86 | } | ||
87 | 70 | ||
88 | if(boot_size > BOOT_SIZE) { | 71 | src += 18; |
89 | jz_log(jz, JZ_LOG_ERROR, "Bootloader is too big"); | 72 | src_len -= 18; |
90 | return JZ_ERR_BAD_FILE_FORMAT; | 73 | |
91 | } | 74 | /* Calculate amount of space that we might need & allocate a buffer: |
75 | * - subtract 4 to account for end of file marker | ||
76 | * - each block is block_size bytes + 8 bytes of header | ||
77 | * - add one to nr_blocks to account for case where file size < block size | ||
78 | * - total size = max uncompressed size of block * nr_blocks | ||
79 | */ | ||
80 | uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1; | ||
81 | uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256); | ||
82 | buffer = jz_buffer_alloc(max_size, NULL); | ||
83 | if(!buffer) | ||
84 | goto error; | ||
92 | 85 | ||
93 | uint8_t* imgdat = (uint8_t*)image_buf; | 86 | /* perform the decompression */ |
94 | memset(&imgdat[SPL_OFFSET], 0xff, SPL_SIZE); | 87 | uint32_t dst_ilen = buffer->size; |
95 | memcpy(&imgdat[SPL_OFFSET], spl_buf, spl_size); | 88 | uint8_t* dst = buffer->data; |
96 | memset(&imgdat[BOOT_OFFSET], 0xff, BOOT_SIZE); | 89 | while(1) { |
97 | memcpy(&imgdat[BOOT_OFFSET], boot_buf, boot_size); | 90 | if(src_len < 4) |
98 | return JZ_SUCCESS; | 91 | goto error; |
99 | } | ||
100 | 92 | ||
101 | #define IMGBUF 0 | 93 | uint32_t out_len = xread32(src); src += 4, src_len -= 4; |
102 | #define SPLBUF 1 | 94 | if(out_len == 0) |
103 | #define BOOTBUF 2 | 95 | break; |
104 | #define NUMBUFS 3 | ||
105 | #define IMGBUF_NAME "image" | ||
106 | #define SPLBUF_NAME "spl" | ||
107 | #define BOOTBUF_NAME "bootloader" | ||
108 | #define FIIOM3K_INIT_WORKSTATE {0} | ||
109 | |||
110 | struct fiiom3k_workstate { | ||
111 | jz_usbdev* dev; | ||
112 | jz_buffer* bufs[NUMBUFS]; | ||
113 | }; | ||
114 | |||
115 | static void fiiom3k_action_cleanup(struct fiiom3k_workstate* state) | ||
116 | { | ||
117 | for(int i = 0; i < NUMBUFS; ++i) | ||
118 | if(state->bufs[i]) | ||
119 | jz_buffer_free(state->bufs[i]); | ||
120 | 96 | ||
121 | if(state->dev) | 97 | if(src_len < 4) |
122 | jz_usb_close(state->dev); | 98 | goto error; |
123 | } | ||
124 | 99 | ||
125 | static int fiiom3k_action_loadbuf(jz_context* jz, jz_paramlist* pl, | 100 | uint32_t in_len = xread32(src); src += 4, src_len -= 4; |
126 | struct fiiom3k_workstate* state, int idx) | 101 | if(in_len > block_size || out_len > block_size || |
127 | { | 102 | in_len == 0 || in_len > out_len) |
128 | const char* const paramnames[] = {IMGBUF_NAME, SPLBUF_NAME, BOOTBUF_NAME}; | 103 | goto error; |
129 | 104 | ||
130 | if(state->bufs[idx]) | 105 | if(src_len < in_len) |
131 | return JZ_SUCCESS; | 106 | goto error; |
132 | 107 | ||
133 | const char* filename = jz_paramlist_get(pl, paramnames[idx]); | 108 | if(in_len < out_len) { |
134 | if(!filename) { | 109 | uint32_t actual_out_len = dst_ilen; |
135 | jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", paramnames[idx]); | 110 | int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL); |
136 | return JZ_ERR_OTHER; | 111 | if(rc != UCL_E_OK) |
137 | } | 112 | goto error; |
113 | if(actual_out_len != out_len) | ||
114 | goto error; | ||
115 | } else { | ||
116 | for(size_t i = 0; i < in_len; ++i) | ||
117 | dst[i] = src[i]; | ||
118 | } | ||
138 | 119 | ||
139 | int rc = jz_buffer_load(&state->bufs[idx], filename); | 120 | src += in_len; |
140 | if(rc < 0) { | 121 | src_len -= in_len; |
141 | jz_log(jz, JZ_LOG_ERROR, "Error reading '%s' file (%d): %s", paramnames[idx], rc, filename); | 122 | dst += out_len; |
142 | return rc; | 123 | dst_ilen -= out_len; |
143 | } | 124 | } |
144 | 125 | ||
145 | return JZ_SUCCESS; | 126 | /* subtract leftover number of bytes to get size of compressed output */ |
127 | *dst_len = buffer->size - dst_ilen; | ||
128 | return buffer; | ||
129 | |||
130 | error: | ||
131 | jz_buffer_free(buffer); | ||
132 | return NULL; | ||
146 | } | 133 | } |
147 | 134 | ||
148 | static int fiiom3k_action_setup(jz_context* jz, jz_paramlist* pl, | 135 | static int m3k_stage1(jz_usbdev* dev, jz_buffer* buf) |
149 | struct fiiom3k_workstate* state) | ||
150 | { | 136 | { |
151 | const jz_device_info* info = jz_get_device_info(JZ_DEVICE_FIIOM3K); | 137 | int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data); |
152 | if(!info) | 138 | if(rc < 0) |
153 | return JZ_ERR_OTHER; | 139 | return rc; |
154 | 140 | ||
155 | int rc = fiiom3k_action_loadbuf(jz, pl, state, SPLBUF); | 141 | return jz_usb_start1(dev, 0xf4001800); |
142 | } | ||
143 | |||
144 | static int m3k_stage2(jz_usbdev* dev, jz_buffer* buf) | ||
145 | { | ||
146 | int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data); | ||
156 | if(rc < 0) | 147 | if(rc < 0) |
157 | return rc; | 148 | return rc; |
158 | 149 | ||
159 | jz_log(jz, JZ_LOG_DETAIL, "Open USB device %04x:%04x", | 150 | rc = jz_usb_flush_caches(dev); |
160 | (unsigned int)info->vendor_id, (unsigned int)info->product_id); | ||
161 | rc = jz_usb_open(jz, &state->dev, info->vendor_id, info->product_id); | ||
162 | if(rc < 0) | 151 | if(rc < 0) |
163 | return rc; | 152 | return rc; |
164 | 153 | ||
165 | jz_log(jz, JZ_LOG_DETAIL, "Setup device for flash access"); | 154 | return jz_usb_start2(dev, 0x80004000); |
166 | jz_buffer* splbuf = state->bufs[SPLBUF]; | ||
167 | return jz_x1000_setup(state->dev, splbuf->size, splbuf->data); | ||
168 | } | 155 | } |
169 | 156 | ||
170 | int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl) | 157 | static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file, |
158 | bool decompress, jz_buffer** buf) | ||
171 | { | 159 | { |
172 | struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; | 160 | jz_buffer* buffer = NULL; |
161 | mtar_header_t h; | ||
173 | int rc; | 162 | int rc; |
174 | 163 | ||
175 | rc = fiiom3k_action_loadbuf(jz, pl, &state, BOOTBUF); | 164 | rc = mtar_find(tar, file, &h); |
176 | if(rc < 0) | 165 | if(rc != MTAR_ESUCCESS) { |
177 | goto error; | 166 | jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc); |
167 | return JZ_ERR_BAD_FILE_FORMAT; | ||
168 | } | ||
178 | 169 | ||
179 | rc = fiiom3k_action_setup(jz, pl, &state); | 170 | buffer = jz_buffer_alloc(h.size, NULL); |
180 | if(rc < 0) | 171 | if(!buffer) |
181 | goto error; | 172 | return JZ_ERR_OUT_OF_MEMORY; |
182 | 173 | ||
183 | jz_log(jz, JZ_LOG_DETAIL, "Reading boot image from device"); | 174 | rc = mtar_read_data(tar, buffer->data, buffer->size); |
184 | rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]); | 175 | if(rc != MTAR_ESUCCESS) { |
185 | if(rc < 0) | 176 | jz_buffer_free(buffer); |
186 | goto error; | 177 | jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", file, rc); |
178 | return JZ_ERR_BAD_FILE_FORMAT; | ||
179 | } | ||
187 | 180 | ||
188 | jz_buffer* img_buf = state.bufs[IMGBUF]; | 181 | if(decompress) { |
189 | const char* backupfile = jz_paramlist_get(pl, "backup"); | 182 | uint32_t dst_len; |
190 | const char* without_backup = jz_paramlist_get(pl, "without-backup"); | 183 | jz_buffer* nbuf = ucl_unpack(buffer->data, buffer->size, &dst_len); |
191 | if(backupfile) { | 184 | jz_buffer_free(buffer); |
192 | jz_log(jz, JZ_LOG_DETAIL, "Backup original boot image to file: %s", backupfile); | 185 | if(!nbuf) { |
193 | rc = jz_buffer_save(img_buf, backupfile); | 186 | jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file); |
194 | if(rc < 0) { | 187 | return JZ_ERR_BAD_FILE_FORMAT; |
195 | jz_log(jz, JZ_LOG_ERROR, "Error saving backup image file (%d): %s", rc, backupfile); | ||
196 | goto error; | ||
197 | } | 188 | } |
198 | } else if(!without_backup || strcmp(without_backup, "yes")) { | ||
199 | jz_log(jz, JZ_LOG_ERROR, "No --backup option given and --without-backup yes not specified"); | ||
200 | jz_log(jz, JZ_LOG_ERROR, "Refusing to flash a new bootloader without taking a backup"); | ||
201 | goto error; | ||
202 | } | ||
203 | 189 | ||
204 | jz_log(jz, JZ_LOG_DETAIL, "Patching image with new SPL/bootloader"); | 190 | /* for simplicity just forget original size of buffer */ |
205 | jz_buffer* boot_buf = state.bufs[BOOTBUF]; | 191 | nbuf->size = dst_len; |
206 | jz_buffer* spl_buf = state.bufs[SPLBUF]; | 192 | buffer = nbuf; |
207 | rc = jz_fiiom3k_patchboot(jz, img_buf->data, img_buf->size, | ||
208 | spl_buf->data, spl_buf->size, | ||
209 | boot_buf->data, boot_buf->size); | ||
210 | if(rc < 0) { | ||
211 | jz_log(jz, JZ_LOG_ERROR, "Error patching image: %d", rc); | ||
212 | goto error; | ||
213 | } | 193 | } |
214 | 194 | ||
215 | jz_log(jz, JZ_LOG_DETAIL, "Writing patched image to device"); | 195 | *buf = buffer; |
216 | rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data); | 196 | return JZ_SUCCESS; |
217 | if(rc < 0) | 197 | } |
218 | goto error; | ||
219 | 198 | ||
220 | rc = JZ_SUCCESS; | 199 | static int m3k_show_version(jz_context* jz, jz_buffer* info_file) |
200 | { | ||
201 | /* Extract the version string and log it for informational purposes */ | ||
202 | char* boot_version = (char*)info_file->data; | ||
203 | char* endpos = memchr(boot_version, '\n', info_file->size); | ||
204 | if(!endpos) { | ||
205 | jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file"); | ||
206 | return JZ_ERR_BAD_FILE_FORMAT; | ||
207 | } | ||
221 | 208 | ||
222 | error: | 209 | *endpos = 0; |
223 | fiiom3k_action_cleanup(&state); | 210 | jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version); |
224 | return rc; | 211 | return JZ_SUCCESS; |
225 | } | 212 | } |
226 | 213 | ||
227 | int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl) | 214 | /** \brief Load the Rockbox bootloader on the FiiO M3K |
215 | * \param dev USB device freshly returned by jz_usb_open() | ||
216 | * \param filename Path to the "bootloader.m3k" file | ||
217 | * \return either JZ_SUCCESS or an error code | ||
218 | */ | ||
219 | int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename) | ||
228 | { | 220 | { |
229 | struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; | 221 | jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL; |
222 | mtar_t tar; | ||
230 | int rc; | 223 | int rc; |
231 | 224 | ||
232 | const char* outfile_path = jz_paramlist_get(pl, IMGBUF_NAME); | 225 | rc = mtar_open(&tar, filename, "r"); |
233 | if(!outfile_path) { | 226 | if(rc != MTAR_ESUCCESS) { |
234 | jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", IMGBUF_NAME); | 227 | jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc); |
235 | rc = JZ_ERR_OTHER; | 228 | return JZ_ERR_OPEN_FILE; |
236 | goto error; | ||
237 | } | 229 | } |
238 | 230 | ||
239 | rc = fiiom3k_action_setup(jz, pl, &state); | 231 | /* Extract all necessary files */ |
240 | if(rc < 0) | 232 | rc = m3k_get_file(dev->jz, &tar, "spl.m3k", false, &spl); |
233 | if(rc != JZ_SUCCESS) | ||
241 | goto error; | 234 | goto error; |
242 | 235 | ||
243 | rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]); | 236 | rc = m3k_get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader); |
244 | if(rc < 0) | 237 | if(rc != JZ_SUCCESS) |
245 | goto error; | 238 | goto error; |
246 | 239 | ||
247 | rc = jz_buffer_save(state.bufs[IMGBUF], outfile_path); | 240 | rc = m3k_get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file); |
248 | if(rc < 0) { | 241 | if(rc != JZ_SUCCESS) |
249 | jz_log(jz, JZ_LOG_ERROR, "Error writing '%s' file (%d): %s", IMGBUF_NAME, rc, outfile_path); | ||
250 | goto error; | 242 | goto error; |
251 | } | ||
252 | |||
253 | rc = JZ_SUCCESS; | ||
254 | 243 | ||
255 | error: | 244 | /* Display the version string */ |
256 | fiiom3k_action_cleanup(&state); | 245 | rc = m3k_show_version(dev->jz, info_file); |
257 | return rc; | 246 | if(rc != JZ_SUCCESS) |
258 | } | ||
259 | |||
260 | int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl) | ||
261 | { | ||
262 | struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; | ||
263 | int rc; | ||
264 | |||
265 | rc = fiiom3k_action_loadbuf(jz, pl, &state, IMGBUF); | ||
266 | if(rc < 0) | ||
267 | goto error; | 247 | goto error; |
268 | 248 | ||
269 | rc = fiiom3k_action_setup(jz, pl, &state); | 249 | /* Stage1 boot of SPL to set up hardware */ |
270 | if(rc < 0) | 250 | rc = m3k_stage1(dev, spl); |
251 | if(rc != JZ_SUCCESS) | ||
271 | goto error; | 252 | goto error; |
272 | 253 | ||
273 | jz_buffer* img_buf = state.bufs[IMGBUF]; | 254 | /* Need a bit of time for SPL to handle init */ |
274 | rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data); | 255 | jz_sleepms(500); |
275 | if(rc < 0) | 256 | |
257 | /* Stage2 boot into the bootloader's recovery menu | ||
258 | * User has to take manual action from there */ | ||
259 | rc = m3k_stage2(dev, bootloader); | ||
260 | if(rc != JZ_SUCCESS) | ||
276 | goto error; | 261 | goto error; |
277 | 262 | ||
278 | rc = JZ_SUCCESS; | 263 | rc = JZ_SUCCESS; |
279 | 264 | ||
280 | error: | 265 | error: |
281 | fiiom3k_action_cleanup(&state); | 266 | if(spl) |
267 | jz_buffer_free(spl); | ||
268 | if(bootloader) | ||
269 | jz_buffer_free(bootloader); | ||
270 | if(info_file) | ||
271 | jz_buffer_free(info_file); | ||
272 | mtar_close(&tar); | ||
282 | return rc; | 273 | return rc; |
283 | } | 274 | } |