forked from len0rd/rockbox
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
This commit is contained in:
parent
44fbb1a593
commit
6a6c6083fa
4 changed files with 143 additions and 3 deletions
|
@ -216,3 +216,99 @@ ssize_t uimage_fd_reader(void* buf, size_t size, void* ctx)
|
|||
int fd = (intptr_t)ctx;
|
||||
return read(fd, buf, size);
|
||||
}
|
||||
|
||||
/* Linux's self-extracting kernels are broken on MIPS. The decompressor stub
|
||||
* doesn't flush caches after extracting the kernel code which can cause the
|
||||
* boot to fail horribly. This has been true since at least 2009 and at the
|
||||
* time of writing (2022) it's *still* broken.
|
||||
*
|
||||
* The FiiO M3K and Shanling Q1 both have broken kernels of this type, so we
|
||||
* work around this by replacing the direct call to the kernel entry point with
|
||||
* a thunk that adds the necessary cache flush.
|
||||
*/
|
||||
uint32_t mips_linux_stub_get_entry(void** code_start, size_t code_size)
|
||||
{
|
||||
/* The jump to the kernel entry point looks like this:
|
||||
*
|
||||
* move a0, s0
|
||||
* move a1, s1
|
||||
* move a2, s2
|
||||
* move a3, s3
|
||||
* ...
|
||||
* la k0, KERNEL_ENTRY
|
||||
* jr k0
|
||||
* --- or in kernels since 2021: ---
|
||||
* la t9, KERNEL_ENTRY
|
||||
* jalr t9
|
||||
*
|
||||
* We're trying to identify this code and decode the kernel entry
|
||||
* point address, and return a suitable address where we can patch
|
||||
* in a call to our thunk.
|
||||
*/
|
||||
|
||||
/* We should only need to scan within the first 128 bytes
|
||||
* but do up to 256 just in case. */
|
||||
uint32_t* start = *code_start;
|
||||
uint32_t* end = start + (MIN(code_size, 256) + 3) / 4;
|
||||
|
||||
/* Scan for the "move aN, sN" sequence */
|
||||
uint32_t* move_instr = start;
|
||||
for(move_instr += 4; move_instr < end; ++move_instr) {
|
||||
if(move_instr[-4] == 0x02002021 && /* move a0, s0 */
|
||||
move_instr[-3] == 0x02202821 && /* move a1, s1 */
|
||||
move_instr[-2] == 0x02403021 && /* move a2, s2 */
|
||||
move_instr[-1] == 0x02603821) /* move a3, s3 */
|
||||
break;
|
||||
}
|
||||
|
||||
if(move_instr == end)
|
||||
return 0;
|
||||
|
||||
/* Now search forward for the next jr/jalr instruction */
|
||||
int jreg = 0;
|
||||
uint32_t* jump_instr = move_instr;
|
||||
for(; jump_instr != end; ++jump_instr) {
|
||||
if((jump_instr[0] & 0xfc1ff83f) == 0xf809 ||
|
||||
(jump_instr[0] & 0xfc00003f) == 0x8) {
|
||||
/* jalr rN */
|
||||
jreg = (jump_instr[0] >> 21) & 0x1f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Need room here for 4 instructions. Assume everything between the
|
||||
* moves and the jump is safe to overwrite; otherwise, we'll need to
|
||||
* take a different approach.
|
||||
*
|
||||
* Count +1 instruction for the branch delay slot and another +1 because
|
||||
* "move_instr" points to the instruction following the last move. */
|
||||
if(jump_instr - move_instr + 2 < 4)
|
||||
return 0;
|
||||
if(!jreg)
|
||||
return 0;
|
||||
|
||||
/* Now scan from the end of the move sequence until the jump instruction
|
||||
* and try to reconstruct the entry address. We check for lui/ori/addiu. */
|
||||
const uint32_t lui_mask = 0xffff0000;
|
||||
const uint32_t lui = 0x3c000000 | (jreg << 16);
|
||||
const uint32_t ori_mask = 0xffff0000;
|
||||
const uint32_t ori = 0x34000000 | (jreg << 21) | (jreg << 16);
|
||||
const uint32_t addiu_mask = 0xffff0000;
|
||||
const uint32_t addiu = 0x24000000 | (jreg << 21) | (jreg << 16);
|
||||
|
||||
/* Can use any initial value here */
|
||||
uint32_t jreg_val = 0xdeadbeef;
|
||||
|
||||
for(uint32_t* instr = move_instr; instr != jump_instr; ++instr) {
|
||||
if((instr[0] & lui_mask) == lui)
|
||||
jreg_val = (instr[0] & 0xffff) << 16;
|
||||
else if((instr[0] & ori_mask) == ori)
|
||||
jreg_val |= instr[0] & 0xffff;
|
||||
else if((instr[0] & addiu_mask) == addiu)
|
||||
jreg_val += instr[0] & 0xffff;
|
||||
}
|
||||
|
||||
/* Success! Probably! */
|
||||
*code_start = move_instr;
|
||||
return jreg_val;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue