/*************************************************************************** * __________ __ ___. * 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 #include #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; }