4b26372591
Inline assembly in RoLO and the FiiO M3K bootloader used 'jr' to jump to a newly loaded Rockbox binary, but incorrectly left the branch delay slot open. That gives GCC an opening to place illegal instrutions, etc, which might cause an unhandled exception. Change-Id: Ia7a561fe530e94a41189d25f18a767c448177960
206 lines
5.8 KiB
C
206 lines
5.8 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2021 Aidan MacDonald
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "config.h"
|
|
#include "nand-x1000.h"
|
|
#include "gpio-x1000.h"
|
|
#include "mmu-mips.h"
|
|
#include <string.h>
|
|
|
|
/* "fiio" in little endian */
|
|
#define BOOTMAGIC 0x6f696966
|
|
|
|
/* Argument structure needed by Linux */
|
|
struct linux_kargs {
|
|
void* arg0;
|
|
void* arg1;
|
|
};
|
|
|
|
#define LINUX_KARGSADDR 0x80004000
|
|
|
|
static const char recovery_cmdline[] = "mem=xxM@0x0\
|
|
no_console_suspend\
|
|
console=ttyS2,115200n8\
|
|
lpj=5009408\
|
|
ip=off";
|
|
|
|
static const char normal_cmdline[] = "mem=64M@0x0\
|
|
no_console_suspend\
|
|
console=ttyS2,115200n8\
|
|
lpj=5009408\
|
|
ip=off\
|
|
init=/linuxrc\
|
|
ubi.mtd=3\
|
|
root=ubi0:rootfs\
|
|
ubi.mtd=4\
|
|
rootfstype=ubifs\
|
|
rw\
|
|
loglevel=8";
|
|
|
|
#define BOOTOPTION_ROCKBOX 0
|
|
#define BOOTOPTION_FIIOLINUX 1
|
|
#define BOOTOPTION_RECOVERY 2
|
|
#define NUM_BOOTOPTIONS 3
|
|
|
|
static const struct bootoption {
|
|
uint32_t nand_addr;
|
|
uint32_t nand_size;
|
|
unsigned long load_addr;
|
|
unsigned long exec_addr;
|
|
const char* cmdline;
|
|
} boot_options[NUM_BOOTOPTIONS] = {
|
|
{
|
|
/* Rockbox: the first unused NAND page is 26 KiB in, and the
|
|
* remainder of the block is unused, giving us 102 KiB to use.
|
|
*/
|
|
.nand_addr = 0x6800,
|
|
.nand_size = 0x19800,
|
|
.load_addr = 0x80003ff8, /* first 8 bytes are bootloader ID */
|
|
.exec_addr = 0x80004000,
|
|
.cmdline = NULL,
|
|
},
|
|
{
|
|
/* Original firmware */
|
|
.nand_addr = 0x20000,
|
|
.nand_size = 0x400000,
|
|
.load_addr = 0x80efffc0,
|
|
.exec_addr = 0x80f00000,
|
|
.cmdline = normal_cmdline,
|
|
},
|
|
{
|
|
/* Recovery image */
|
|
.nand_addr = 0x420000,
|
|
.nand_size = 0x500000,
|
|
.load_addr = 0x80efffc0,
|
|
.exec_addr = 0x80f00000,
|
|
.cmdline = recovery_cmdline,
|
|
},
|
|
};
|
|
|
|
/* Simple diagnostic if something goes wrong -- a little nicer than wondering
|
|
* what's going on when the machine hangs
|
|
*/
|
|
void die(void)
|
|
{
|
|
const int pin = (1 << 24);
|
|
|
|
/* Turn on button light */
|
|
jz_clr(GPIO_INT(GPIO_C), pin);
|
|
jz_set(GPIO_MSK(GPIO_C), pin);
|
|
jz_clr(GPIO_PAT1(GPIO_C), pin);
|
|
jz_set(GPIO_PAT0(GPIO_C), pin);
|
|
|
|
while(1) {
|
|
/* Turn it off */
|
|
mdelay(100);
|
|
jz_set(GPIO_PAT0(GPIO_C), pin);
|
|
|
|
/* Turn it on */
|
|
mdelay(100);
|
|
jz_clr(GPIO_PAT0(GPIO_C), pin);
|
|
}
|
|
}
|
|
|
|
/* Boot select button state must remain stable for this duration
|
|
* before the choice will be accepted. Currently 100ms.
|
|
*/
|
|
#define BTN_STABLE_TIME (100 * (X1000_EXCLK_FREQ / 4000))
|
|
|
|
int get_boot_option(void)
|
|
{
|
|
const uint32_t pinmask = (1 << 17) | (1 << 19);
|
|
|
|
uint32_t pin = 1, lastpin = 0;
|
|
uint32_t deadline = 0;
|
|
|
|
/* Configure the button GPIOs as inputs */
|
|
gpio_config(GPIO_A, pinmask, GPIO_INPUT);
|
|
|
|
/* Poll the pins for a short duration to detect a keypress */
|
|
do {
|
|
lastpin = pin;
|
|
pin = ~REG_GPIO_PIN(GPIO_A) & pinmask;
|
|
if(pin != lastpin) {
|
|
/* This will always be set on the first iteration */
|
|
deadline = __ost_read32() + BTN_STABLE_TIME;
|
|
}
|
|
} while(__ost_read32() < deadline);
|
|
|
|
/* Play button boots original firmware */
|
|
if(pin == (1 << 17))
|
|
return BOOTOPTION_FIIOLINUX;
|
|
|
|
/* Volume up boots recovery */
|
|
if(pin == (1 << 19))
|
|
return BOOTOPTION_RECOVERY;
|
|
|
|
/* Default is to boot Rockbox */
|
|
return BOOTOPTION_ROCKBOX;
|
|
}
|
|
|
|
void spl_main(void)
|
|
{
|
|
/* Get user boot option */
|
|
int booti = get_boot_option();
|
|
const struct bootoption* opt = &boot_options[booti];
|
|
|
|
/* Load selected firmware from flash */
|
|
if(nand_open())
|
|
die();
|
|
if(nand_read_bytes(opt->nand_addr, opt->nand_size, (void*)opt->load_addr))
|
|
die();
|
|
|
|
if(booti == BOOTOPTION_ROCKBOX) {
|
|
/* If bootloader is not installed, return back to boot ROM.
|
|
* Also read in the first eraseblock of NAND flash so it can be
|
|
* dumped back over USB.
|
|
*/
|
|
if(*(unsigned*)(opt->load_addr + 4) != BOOTMAGIC) {
|
|
nand_read_bytes(0, 128 * 1024, (void*)0x80000000);
|
|
commit_discard_idcache();
|
|
return;
|
|
}
|
|
} else {
|
|
/* TODO: Linux boot not implemented yet
|
|
*
|
|
* - Have to initialize UART2, as it's used for the serial console
|
|
* - Must initialize APLL and change clocks over
|
|
* - There are some other clocks which need to be initialized
|
|
* - We should turn off OST since the OF SPL does not turn it on
|
|
*/
|
|
die();
|
|
}
|
|
|
|
if(boot_options[booti].cmdline) {
|
|
/* Handle Linux command line arguments */
|
|
struct linux_kargs* kargs = (struct linux_kargs*)LINUX_KARGSADDR;
|
|
kargs->arg0 = 0;
|
|
kargs->arg1 = (void*)boot_options[booti].cmdline;
|
|
}
|
|
|
|
/* Flush caches and jump to address */
|
|
void* execaddr = (void*)opt->exec_addr;
|
|
commit_discard_idcache();
|
|
__asm__ __volatile__ ("jr %0\n"
|
|
"nop\n"
|
|
:: "r"(execaddr));
|
|
__builtin_unreachable();
|
|
}
|