summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2022-03-16 15:39:18 +0000
committerAidan MacDonald <amachronic@protonmail.com>2022-03-24 23:40:07 +0000
commit6a6c6083fa69575334282d0c8f5dd688a2282188 (patch)
tree17723690dd5ad1ee55f3a2c1651a138b30fb46a1
parent44fbb1a59363a464d637d93656e9b29858451550 (diff)
downloadrockbox-6a6c6083fa69575334282d0c8f5dd688a2282188.tar.gz
rockbox-6a6c6083fa69575334282d0c8f5dd688a2282188.zip
x1000: bootloader: fix Linux self-extracting kernel boot
Basically, there's longstanding bug in Linux with self-extracting kernels on MIPS which just happened to manifest now on the M3K as a hang on boot. The fix is applied to the M3K and Q1 since they both use this type of kernel image. Change-Id: I17d2bad6eebd677cd6d2e0bf146450c71fcf1229
-rw-r--r--bootloader/x1000/boot.c42
-rw-r--r--bootloader/x1000/x1000bootloader.h5
-rw-r--r--firmware/export/linuxboot.h3
-rw-r--r--firmware/linuxboot.c96
4 files changed, 143 insertions, 3 deletions
diff --git a/bootloader/x1000/boot.c b/bootloader/x1000/boot.c
index 153c2277aa..d6dfd4a193 100644
--- a/bootloader/x1000/boot.c
+++ b/bootloader/x1000/boot.c
@@ -148,6 +148,35 @@ void boot_linux(void)
148 * Be careful when modifying this code. 148 * Be careful when modifying this code.
149 */ 149 */
150 150
151#if defined(FIIO_M3K) || defined(SHANLING_Q1)
152uint32_t saved_kernel_entry __attribute__((section(".idata")));
153void kernel_thunk(long, long, long, long) __attribute__((section(".icode")));
154
155void kernel_thunk(long a0, long a1, long a2, long a3)
156{
157 /* cache flush */
158 commit_discard_idcache();
159
160 /* now we can jump to the kernel */
161 typedef void(*entry_fn)(long, long, long, long);
162 entry_fn fn = (entry_fn)saved_kernel_entry;
163 fn(a0, a1, a2, a3);
164 while(1);
165}
166
167static void patch_stub_call(void* patch_addr)
168{
169 uint32_t* code = patch_addr;
170 uint32_t stub_addr = (uint32_t)(void*)kernel_thunk;
171
172 /* generate call to stub */
173 code[0] = 0x3c190000 | (stub_addr >> 16); /* lui t9, stub_hi */
174 code[1] = 0x37390000 | (stub_addr & 0xffff); /* ori t9, t9, stub_lo */
175 code[2] = 0x0320f809; /* jalr t9 */
176 code[3] = 0x00000000; /* nop */
177}
178#endif
179
151static __attribute__((unused)) 180static __attribute__((unused))
152void boot_of_helper(uint32_t addr, uint32_t flash_size, const char* args) 181void boot_of_helper(uint32_t addr, uint32_t flash_size, const char* args)
153{ 182{
@@ -157,6 +186,19 @@ void boot_of_helper(uint32_t addr, uint32_t flash_size, const char* args)
157 if(handle < 0) 186 if(handle < 0)
158 return; 187 return;
159 188
189#if defined(FIIO_M3K) || defined(SHANLING_Q1)
190 /* Fix for targets that use self-extracting kernel images */
191 void* jump_addr = core_get_data(handle);
192 uint32_t entry_addr = mips_linux_stub_get_entry(&jump_addr, img_length);
193 if(entry_addr >= 0xa0000000 || entry_addr < 0x80000000) {
194 splash2(5*HZ, "Kernel patch failed", "Please send bugreport");
195 return;
196 }
197
198 saved_kernel_entry = entry_addr;
199 patch_stub_call(jump_addr);
200#endif
201
160 gui_shutdown(); 202 gui_shutdown();
161 203
162 x1000_dualboot_load_pdma_fw(); 204 x1000_dualboot_load_pdma_fw();
diff --git a/bootloader/x1000/x1000bootloader.h b/bootloader/x1000/x1000bootloader.h
index 587a820eaf..10f6c6e730 100644
--- a/bootloader/x1000/x1000bootloader.h
+++ b/bootloader/x1000/x1000bootloader.h
@@ -41,13 +41,12 @@ struct uimage_header;
41# define BL_SELECT_NAME "PLAY" 41# define BL_SELECT_NAME "PLAY"
42# define BL_QUIT_NAME "POWER" 42# define BL_QUIT_NAME "POWER"
43# define BOOTBACKUP_FILE "/fiiom3k-boot.bin" 43# define BOOTBACKUP_FILE "/fiiom3k-boot.bin"
44// FIXME: OF kernel hangs on the m3k 44# define OF_PLAYER_NAME "FiiO player"
45//# define OF_PLAYER_NAME "FiiO player"
46# define OF_PLAYER_ADDR 0x20000 45# define OF_PLAYER_ADDR 0x20000
47# define OF_PLAYER_LENGTH (4 * 1024 * 1024) 46# define OF_PLAYER_LENGTH (4 * 1024 * 1024)
48# define OF_PLAYER_ARGS OF_RECOVERY_ARGS \ 47# define OF_PLAYER_ARGS OF_RECOVERY_ARGS \
49 " init=/linuxrc ubi.mtd=3 root=ubi0:rootfs ubi.mtd=4 rootfstype=ubifs rw loglevel=8" 48 " init=/linuxrc ubi.mtd=3 root=ubi0:rootfs ubi.mtd=4 rootfstype=ubifs rw loglevel=8"
50//# define OF_RECOVERY_NAME "FiiO recovery" 49# define OF_RECOVERY_NAME "FiiO recovery"
51# define OF_RECOVERY_ADDR 0x420000 50# define OF_RECOVERY_ADDR 0x420000
52# define OF_RECOVERY_LENGTH (5 * 1024 * 1024) 51# define OF_RECOVERY_LENGTH (5 * 1024 * 1024)
53# define OF_RECOVERY_ARGS \ 52# define OF_RECOVERY_ARGS \
diff --git a/firmware/export/linuxboot.h b/firmware/export/linuxboot.h
index 7dbc213012..de6f24bf57 100644
--- a/firmware/export/linuxboot.h
+++ b/firmware/export/linuxboot.h
@@ -186,4 +186,7 @@ int uimage_load(struct uimage_header* uh, size_t* out_size,
186 */ 186 */
187ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx); 187ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx);
188 188
189/* helper for patching broken self-extracting kernels on MIPS */
190uint32_t mips_linux_stub_get_entry(void** code_start, size_t code_size);
191
189#endif /* __LINUXBOOT_H__ */ 192#endif /* __LINUXBOOT_H__ */
diff --git a/firmware/linuxboot.c b/firmware/linuxboot.c
index 5b6ab314b3..aa907ac7bb 100644
--- a/firmware/linuxboot.c
+++ b/firmware/linuxboot.c
@@ -216,3 +216,99 @@ ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx)
216 int fd = (intptr_t)ctx; 216 int fd = (intptr_t)ctx;
217 return read(fd, buf, size); 217 return read(fd, buf, size);
218} 218}
219
220/* Linux's self-extracting kernels are broken on MIPS. The decompressor stub
221 * doesn't flush caches after extracting the kernel code which can cause the
222 * boot to fail horribly. This has been true since at least 2009 and at the
223 * time of writing (2022) it's *still* broken.
224 *
225 * The FiiO M3K and Shanling Q1 both have broken kernels of this type, so we
226 * work around this by replacing the direct call to the kernel entry point with
227 * a thunk that adds the necessary cache flush.
228 */
229uint32_t mips_linux_stub_get_entry(void** code_start, size_t code_size)
230{
231 /* The jump to the kernel entry point looks like this:
232 *
233 * move a0, s0
234 * move a1, s1
235 * move a2, s2
236 * move a3, s3
237 * ...
238 * la k0, KERNEL_ENTRY
239 * jr k0
240 * --- or in kernels since 2021: ---
241 * la t9, KERNEL_ENTRY
242 * jalr t9
243 *
244 * We're trying to identify this code and decode the kernel entry
245 * point address, and return a suitable address where we can patch
246 * in a call to our thunk.
247 */
248
249 /* We should only need to scan within the first 128 bytes
250 * but do up to 256 just in case. */
251 uint32_t* start = *code_start;
252 uint32_t* end = start + (MIN(code_size, 256) + 3) / 4;
253
254 /* Scan for the "move aN, sN" sequence */
255 uint32_t* move_instr = start;
256 for(move_instr += 4; move_instr < end; ++move_instr) {
257 if(move_instr[-4] == 0x02002021 && /* move a0, s0 */
258 move_instr[-3] == 0x02202821 && /* move a1, s1 */
259 move_instr[-2] == 0x02403021 && /* move a2, s2 */
260 move_instr[-1] == 0x02603821) /* move a3, s3 */
261 break;
262 }
263
264 if(move_instr == end)
265 return 0;
266
267 /* Now search forward for the next jr/jalr instruction */
268 int jreg = 0;
269 uint32_t* jump_instr = move_instr;
270 for(; jump_instr != end; ++jump_instr) {
271 if((jump_instr[0] & 0xfc1ff83f) == 0xf809 ||
272 (jump_instr[0] & 0xfc00003f) == 0x8) {
273 /* jalr rN */
274 jreg = (jump_instr[0] >> 21) & 0x1f;
275 break;
276 }
277 }
278
279 /* Need room here for 4 instructions. Assume everything between the
280 * moves and the jump is safe to overwrite; otherwise, we'll need to
281 * take a different approach.
282 *
283 * Count +1 instruction for the branch delay slot and another +1 because
284 * "move_instr" points to the instruction following the last move. */
285 if(jump_instr - move_instr + 2 < 4)
286 return 0;
287 if(!jreg)
288 return 0;
289
290 /* Now scan from the end of the move sequence until the jump instruction
291 * and try to reconstruct the entry address. We check for lui/ori/addiu. */
292 const uint32_t lui_mask = 0xffff0000;
293 const uint32_t lui = 0x3c000000 | (jreg << 16);
294 const uint32_t ori_mask = 0xffff0000;
295 const uint32_t ori = 0x34000000 | (jreg << 21) | (jreg << 16);
296 const uint32_t addiu_mask = 0xffff0000;
297 const uint32_t addiu = 0x24000000 | (jreg << 21) | (jreg << 16);
298
299 /* Can use any initial value here */
300 uint32_t jreg_val = 0xdeadbeef;
301
302 for(uint32_t* instr = move_instr; instr != jump_instr; ++instr) {
303 if((instr[0] & lui_mask) == lui)
304 jreg_val = (instr[0] & 0xffff) << 16;
305 else if((instr[0] & ori_mask) == ori)
306 jreg_val |= instr[0] & 0xffff;
307 else if((instr[0] & addiu_mask) == addiu)
308 jreg_val += instr[0] & 0xffff;
309 }
310
311 /* Success! Probably! */
312 *code_start = move_instr;
313 return jreg_val;
314}