forked from len0rd/rockbox
257 lines
7.8 KiB
C
257 lines
7.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 "jztool.h"
|
|
#include "jztool_private.h"
|
|
#include "microtar-stdio.h"
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#define X1000_TCSM_BASE 0xf4000000
|
|
|
|
#define X1000_SPL_LOAD_ADDR (X1000_TCSM_BASE + 0x1000)
|
|
#define X1000_SPL_EXEC_ADDR (X1000_TCSM_BASE + 0x1800)
|
|
|
|
#define X1000_STANDARD_DRAM_BASE 0x80004000
|
|
|
|
#define HDR_BEGIN 128 /* header must begin within this many bytes */
|
|
#define HDR_LEN 256 /* header length cannot exceed this */
|
|
|
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
|
|
/* search for header value, label must be a 4-character string.
|
|
* Returns the found value or 0 if the label wasn't found. */
|
|
static uint32_t search_header(const unsigned char* source, size_t length,
|
|
const char* label)
|
|
{
|
|
size_t search_len = MIN(length, HDR_BEGIN);
|
|
if(search_len < 8)
|
|
return 0;
|
|
search_len -= 7;
|
|
|
|
/* find the beginning marker */
|
|
size_t i;
|
|
for(i = 8; i < search_len; i += 4)
|
|
if(!memcmp(&source[i], "BEGINHDR", 8))
|
|
break;
|
|
if(i >= search_len)
|
|
return 0;
|
|
i += 8;
|
|
|
|
/* search within the header */
|
|
search_len = MIN(length, i + HDR_LEN) - 7;
|
|
for(; i < search_len; i += 8) {
|
|
if(!memcmp(&source[i], "ENDH", 4)) {
|
|
break;
|
|
} else if(!memcmp(&source[i], label, 4)) {
|
|
i += 4;
|
|
/* read little-endian value */
|
|
uint32_t ret = source[i];
|
|
ret |= source[i+1] << 8;
|
|
ret |= source[i+2] << 16;
|
|
ret |= source[i+3] << 24;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int run_stage1(jz_usbdev* dev, jz_buffer* buf)
|
|
{
|
|
int rc = jz_usb_send(dev, X1000_SPL_LOAD_ADDR, buf->size, buf->data);
|
|
if(rc < 0)
|
|
return rc;
|
|
|
|
return jz_usb_start1(dev, X1000_SPL_EXEC_ADDR);
|
|
}
|
|
|
|
static int run_stage2(jz_usbdev* dev, jz_buffer* buf)
|
|
{
|
|
uint32_t load_addr = search_header(buf->data, buf->size, "LOAD");
|
|
if(!load_addr)
|
|
load_addr = X1000_STANDARD_DRAM_BASE;
|
|
|
|
int rc = jz_usb_send(dev, load_addr, buf->size, buf->data);
|
|
if(rc < 0)
|
|
return rc;
|
|
|
|
rc = jz_usb_flush_caches(dev);
|
|
if(rc < 0)
|
|
return rc;
|
|
|
|
return jz_usb_start2(dev, load_addr);
|
|
}
|
|
|
|
enum {
|
|
F_DECOMPRESS = 0x01,
|
|
F_OPTIONAL = 0x02,
|
|
};
|
|
|
|
static int get_file(jz_context* jz, mtar_t* tar, const char* file,
|
|
unsigned int flags, jz_buffer** buf)
|
|
{
|
|
jz_buffer* buffer = NULL;
|
|
const mtar_header_t* h;
|
|
int rc;
|
|
|
|
rc = mtar_find(tar, file);
|
|
if(rc != MTAR_ESUCCESS) {
|
|
if(!(flags & F_OPTIONAL))
|
|
jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc);
|
|
return JZ_ERR_OPEN_FILE;
|
|
}
|
|
|
|
h = mtar_get_header(tar);
|
|
buffer = jz_buffer_alloc(h->size, NULL);
|
|
if(!buffer)
|
|
return JZ_ERR_OUT_OF_MEMORY;
|
|
|
|
rc = mtar_read_data(tar, buffer->data, buffer->size);
|
|
if(rc < 0 || (unsigned)rc != buffer->size) {
|
|
jz_buffer_free(buffer);
|
|
jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", file, rc);
|
|
return JZ_ERR_BAD_FILE_FORMAT;
|
|
}
|
|
|
|
if(flags & F_DECOMPRESS) {
|
|
uint32_t dst_len;
|
|
jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len);
|
|
jz_buffer_free(buffer);
|
|
if(!nbuf) {
|
|
jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file);
|
|
return JZ_ERR_BAD_FILE_FORMAT;
|
|
}
|
|
|
|
/* for simplicity just forget original size of buffer */
|
|
nbuf->size = dst_len;
|
|
buffer = nbuf;
|
|
}
|
|
|
|
*buf = buffer;
|
|
return JZ_SUCCESS;
|
|
}
|
|
|
|
static int show_version(jz_context* jz, jz_buffer* info_file)
|
|
{
|
|
/* Extract the version string and log it for informational purposes */
|
|
char* boot_version = (char*)info_file->data;
|
|
char* endpos = memchr(boot_version, '\n', info_file->size);
|
|
if(!endpos) {
|
|
jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file");
|
|
return JZ_ERR_BAD_FILE_FORMAT;
|
|
}
|
|
|
|
*endpos = 0;
|
|
jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version);
|
|
return JZ_SUCCESS;
|
|
}
|
|
|
|
/** \brief Load the Rockbox bootloader on an X1000 device
|
|
* \param dev USB device freshly returned by jz_usb_open()
|
|
* \param filename Path to the "bootloader.target" file
|
|
* \return either JZ_SUCCESS or an error code
|
|
*/
|
|
int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename)
|
|
{
|
|
const jz_device_info* dev_info;
|
|
char spl_filename[32];
|
|
jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
|
|
mtar_t tar;
|
|
int rc;
|
|
|
|
/* In retrospect using a model-dependent archive format was not a good
|
|
* idea, but it's not worth fixing just yet... */
|
|
dev_info = jz_get_device_info(type);
|
|
if(!dev_info)
|
|
return JZ_ERR_OTHER;
|
|
/* use of sprintf is safe since file_ext is short */
|
|
sprintf(spl_filename, "spl.%s", dev_info->file_ext);
|
|
|
|
/* Now open the archive */
|
|
rc = mtar_open(&tar, filename, "rb");
|
|
if(rc != MTAR_ESUCCESS) {
|
|
jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
|
|
return JZ_ERR_OPEN_FILE;
|
|
}
|
|
|
|
/* Extract all necessary files */
|
|
rc = get_file(dev->jz, &tar, spl_filename, false, &spl);
|
|
if(rc != JZ_SUCCESS)
|
|
goto error;
|
|
|
|
/* - A bootloader2.ucl binary should carry the LOAD header to define its
|
|
* load address. This name must be used when the load address is not
|
|
* equal to 0x80004000 to ensure old jztools will not try to load it.
|
|
*
|
|
* - The bootloader.ucl name must only be used when the binary loads at
|
|
* 0x80004000 and can be booted by old versions of jztool.
|
|
*/
|
|
const char* bl_files[2] = {"bootloader2.ucl", "bootloader.ucl"};
|
|
for(int i = 0; i < 2; ++i) {
|
|
rc = get_file(dev->jz, &tar, bl_files[i],
|
|
F_DECOMPRESS|F_OPTIONAL, &bootloader);
|
|
if(rc == JZ_SUCCESS)
|
|
break;
|
|
else if(rc != JZ_ERR_OPEN_FILE)
|
|
goto error;
|
|
}
|
|
|
|
if(rc != JZ_SUCCESS) {
|
|
jz_log(dev->jz, JZ_LOG_ERROR, "no bootloader binary found", filename);
|
|
goto error;
|
|
}
|
|
|
|
rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
|
|
if(rc != JZ_SUCCESS)
|
|
goto error;
|
|
|
|
/* Display the version string */
|
|
rc = show_version(dev->jz, info_file);
|
|
if(rc != JZ_SUCCESS)
|
|
goto error;
|
|
|
|
/* Stage1 boot of SPL to set up hardware */
|
|
rc = run_stage1(dev, spl);
|
|
if(rc != JZ_SUCCESS)
|
|
goto error;
|
|
|
|
/* Need a bit of time for SPL to handle init */
|
|
jz_sleepms(500);
|
|
|
|
/* Stage2 boot into the bootloader's recovery menu
|
|
* User has to take manual action from there */
|
|
rc = run_stage2(dev, bootloader);
|
|
if(rc != JZ_SUCCESS)
|
|
goto error;
|
|
|
|
rc = JZ_SUCCESS;
|
|
|
|
error:
|
|
if(spl)
|
|
jz_buffer_free(spl);
|
|
if(bootloader)
|
|
jz_buffer_free(bootloader);
|
|
if(info_file)
|
|
jz_buffer_free(info_file);
|
|
mtar_close(&tar);
|
|
return rc;
|
|
}
|