rockbox/bootloader/x1000/boot.c

253 lines
6.5 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021-2022 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 "x1000bootloader.h"
#include "core_alloc.h"
#include "system.h"
#include "kernel.h"
#include "power.h"
#include "file.h"
#include "linuxboot.h"
#include "boot-x1000.h"
#include <ctype.h>
#include <sys/types.h>
#if defined(EROS_QN)
# include "lcd-x1000.c"
# include "backlight-target.h"
#endif
void boot_rockbox(void)
{
size_t length;
int handle = load_rockbox(BOOTFILE, &length);
if(handle < 0)
return;
gui_shutdown();
x1000_boot_rockbox(core_get_data(handle), length);
}
void shutdown(void)
{
splashf(HZ, "Shutting down");
power_off();
while(1);
}
void reboot(void)
{
splashf(HZ, "Rebooting");
system_reboot();
while(1);
}
/*
* boot_linux() is intended for mainline kernels, and as such it
* should not need any major target-specific modifications.
*/
static int read_linux_args(const char* filename)
{
if(check_disk(false) != DISK_PRESENT)
return -1;
int ret;
size_t max_size;
int handle = core_alloc_maximum("args", &max_size, &buflib_ops_locked);
if(handle <= 0) {
splashf(5*HZ, "Out of memory");
return -2;
}
int fd = open(filename, O_RDONLY);
if(fd < 0) {
splashf(5*HZ, "Can't open args file\n%s", filename);
ret = -3;
goto err_free;
}
/* this isn't 100% correct but will be good enough */
off_t fsize = filesize(fd);
if(fsize < 0 || fsize+1 > (off_t)max_size) {
splashf(5*HZ, "Arguments too long");
ret = -4;
goto err_close;
}
char* buf = core_get_data(handle);
core_shrink(handle, buf, fsize+1);
ssize_t rdres = read(fd, buf, fsize);
close(fd);
if(rdres != (ssize_t)fsize) {
splashf(5*HZ, "Can't read args file");
ret = -5;
goto err_free;
}
/* append a null terminator */
char* end = buf + fsize;
*end = 0;
/* change all newlines, etc, to spaces */
for(; buf != end; ++buf)
if(isspace(*buf))
*buf = ' ';
return handle;
err_close:
close(fd);
err_free:
core_free(handle);
return ret;
}
/*
* Provisional linux loading function: kernel is at "/uImage",
* contents of "/linux_cmdline.txt" are used as kernel arguments.
*/
void boot_linux(void)
{
struct uimage_header uh;
size_t img_length;
int handle = load_uimage_file("/uImage", &uh, &img_length);
if(handle < 0)
return;
int args_handle = read_linux_args("/linux_cmdline.txt");
if(args_handle < 0) {
core_free(handle);
return;
}
x1000_boot_linux(core_get_data(handle), img_length,
(void*)uimage_get_load(&uh),
(void*)uimage_get_ep(&uh),
core_get_data(args_handle));
}
/*
* WARNING: Original firmware can be finicky.
* Be careful when modifying this code.
*/
#if defined(FIIO_M3K) || defined(SHANLING_Q1)
uint32_t saved_kernel_entry __attribute__((section(".idata")));
void kernel_thunk(long, long, long, long) __attribute__((section(".icode")));
void kernel_thunk(long a0, long a1, long a2, long a3)
{
/* cache flush */
commit_discard_idcache();
/* now we can jump to the kernel */
typedef void(*entry_fn)(long, long, long, long);
entry_fn fn = (entry_fn)saved_kernel_entry;
fn(a0, a1, a2, a3);
while(1);
}
static void patch_stub_call(void* patch_addr)
{
uint32_t* code = patch_addr;
uint32_t stub_addr = (uint32_t)(void*)kernel_thunk;
/* generate call to stub */
code[0] = 0x3c190000 | (stub_addr >> 16); /* lui t9, stub_hi */
code[1] = 0x37390000 | (stub_addr & 0xffff); /* ori t9, t9, stub_lo */
code[2] = 0x0320f809; /* jalr t9 */
code[3] = 0x00000000; /* nop */
}
#endif
static __attribute__((unused))
void boot_of_helper(uint32_t addr, uint32_t flash_size, const char* args)
{
struct uimage_header uh;
size_t img_length;
int handle = load_uimage_flash(addr, flash_size, &uh, &img_length);
if(handle < 0)
return;
#if defined(FIIO_M3K) || defined(SHANLING_Q1)
/* Fix for targets that use self-extracting kernel images */
void* jump_addr = core_get_data(handle);
uint32_t entry_addr = mips_linux_stub_get_entry(&jump_addr, img_length);
if(entry_addr >= 0xa0000000 || entry_addr < 0x80000000) {
splashf(5*HZ, "Kernel patch failed\nPlease send bugreport");
return;
}
saved_kernel_entry = entry_addr;
patch_stub_call(jump_addr);
#endif
gui_shutdown();
/* The Eros Q needs the LCD initialized in the bootloader */
#if defined(EROS_QN)
/* enable LCD if it's not yet on, but keep the backlight off to avoid a white flash */
init_lcd();
backlight_hw_off();
/* TODO: this doesn't work for some reason */
//lcd_clear_display();
//lcd_update();
//lcd_wait_frame();
/* disable irq and dma */
lcd_enable(false);
/* set up LCD in a config compatible with OF */
lcd_tgt_enable_of(1);
#endif
x1000_dualboot_load_pdma_fw();
x1000_dualboot_cleanup();
x1000_dualboot_init_clocktree();
x1000_dualboot_init_uart2();
x1000_boot_linux(core_get_data(handle), img_length,
(void*)uimage_get_load(&uh),
(void*)uimage_get_ep(&uh), args);
}
void boot_of_player(void)
{
#if defined(OF_PLAYER_ADDR)
boot_of_helper(OF_PLAYER_ADDR, OF_PLAYER_LENGTH, OF_PLAYER_ARGS);
#else
splashf(HZ, "Not supported");
#endif
}
void boot_of_recovery(void)
{
#if defined(OF_RECOVERY_ADDR)
boot_of_helper(OF_RECOVERY_ADDR, OF_RECOVERY_LENGTH, OF_RECOVERY_ARGS);
#else
splashf(HZ, "Not supported");
#endif
}