diff options
author | Aidan MacDonald <amachronic@protonmail.com> | 2022-03-16 15:39:18 +0000 |
---|---|---|
committer | Aidan MacDonald <amachronic@protonmail.com> | 2022-03-24 23:40:07 +0000 |
commit | 6a6c6083fa69575334282d0c8f5dd688a2282188 (patch) | |
tree | 17723690dd5ad1ee55f3a2c1651a138b30fb46a1 | |
parent | 44fbb1a59363a464d637d93656e9b29858451550 (diff) | |
download | rockbox-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.c | 42 | ||||
-rw-r--r-- | bootloader/x1000/x1000bootloader.h | 5 | ||||
-rw-r--r-- | firmware/export/linuxboot.h | 3 | ||||
-rw-r--r-- | firmware/linuxboot.c | 96 |
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) | ||
152 | uint32_t saved_kernel_entry __attribute__((section(".idata"))); | ||
153 | void kernel_thunk(long, long, long, long) __attribute__((section(".icode"))); | ||
154 | |||
155 | void 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 | |||
167 | static 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 | |||
151 | static __attribute__((unused)) | 180 | static __attribute__((unused)) |
152 | void boot_of_helper(uint32_t addr, uint32_t flash_size, const char* args) | 181 | void 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 | */ |
187 | ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx); | 187 | ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx); |
188 | 188 | ||
189 | /* helper for patching broken self-extracting kernels on MIPS */ | ||
190 | uint32_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 | */ | ||
229 | uint32_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 | } | ||