From 740a50687f67f3684e4b5698f8f30e3adebb8f6e Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 17 Jul 2021 19:35:09 +0100 Subject: jztool: add support for Shanling Q1 and Eros Q Change-Id: I8e93162d1a9d82e9d132219f2803b1856d24ae6c --- rbutil/jztool/src/device_info.c | 72 +++++++--- rbutil/jztool/src/fiiom3k.c | 274 -------------------------------------- rbutil/jztool/src/identify_file.c | 37 +++-- rbutil/jztool/src/ucl_unpack.c | 128 ++++++++++++++++++ rbutil/jztool/src/usb.c | 40 +++++- rbutil/jztool/src/x1000.c | 179 +++++++++++++++++++++++++ 6 files changed, 418 insertions(+), 312 deletions(-) delete mode 100644 rbutil/jztool/src/fiiom3k.c create mode 100644 rbutil/jztool/src/ucl_unpack.c create mode 100644 rbutil/jztool/src/x1000.c (limited to 'rbutil/jztool/src') diff --git a/rbutil/jztool/src/device_info.c b/rbutil/jztool/src/device_info.c index 5ce3899262..cc431959ca 100644 --- a/rbutil/jztool/src/device_info.c +++ b/rbutil/jztool/src/device_info.c @@ -22,39 +22,58 @@ #include "jztool.h" #include -static const jz_device_info infotable[] = { - { +static const jz_device_info infotable[JZ_NUM_DEVICES] = { + [JZ_DEVICE_FIIOM3K] = { .name = "fiiom3k", + .file_ext = "m3k", .description = "FiiO M3K", .device_type = JZ_DEVICE_FIIOM3K, .cpu_type = JZ_CPU_X1000, + .vendor_id = 0x2972, + .product_id = 0x0003, + }, + [JZ_DEVICE_SHANLINGQ1] = { + .name = "shanlingq1", + .file_ext = "q1", + .description = "Shanling Q1", + .device_type = JZ_DEVICE_SHANLINGQ1, + .cpu_type = JZ_CPU_X1000, + .vendor_id = 0x0525, + .product_id = 0xa4a5, + }, + [JZ_DEVICE_EROSQ] = { + .name = "erosq", + .file_ext = "erosq", + .description = "AIGO Eros Q", + .device_type = JZ_DEVICE_EROSQ, + .cpu_type = JZ_CPU_X1000, + .vendor_id = 0xc502, + .product_id = 0x0023, + }, +}; + +static const jz_cpu_info cputable[JZ_NUM_CPUS] = { + [JZ_CPU_X1000] = { + .info_str = "X1000_v1", .vendor_id = 0xa108, .product_id = 0x1000, + .stage1_load_addr = 0xf4001000, + .stage1_exec_addr = 0xf4001800, + .stage2_load_addr = 0x80004000, + .stage2_exec_addr = 0x80004000, }, }; -static const int infotable_size = sizeof(infotable)/sizeof(struct jz_device_info); - -/** \brief Get the number of entries in the device info list */ -int jz_get_num_device_info(void) -{ - return infotable_size; -} - /** \brief Lookup info for a device by type, returns NULL if not found. */ const jz_device_info* jz_get_device_info(jz_device_type type) { - for(int i = 0; i < infotable_size; ++i) - if(infotable[i].device_type == type) - return &infotable[i]; - - return NULL; + return jz_get_device_info_indexed(type); } /** \brief Lookup info for a device by name, returns NULL if not found. */ const jz_device_info* jz_get_device_info_named(const char* name) { - for(int i = 0; i < infotable_size; ++i) + for(int i = 0; i < JZ_NUM_DEVICES; ++i) if(!strcmp(infotable[i].name, name)) return &infotable[i]; @@ -64,8 +83,27 @@ const jz_device_info* jz_get_device_info_named(const char* name) /** \brief Get a device info entry by index, returns NULL if out of range. */ const jz_device_info* jz_get_device_info_indexed(int index) { - if(index < infotable_size) + if(index < JZ_NUM_DEVICES) return &infotable[index]; else return NULL; } + +/** \brief Lookup info for a CPU, returns NULL if not found. */ +const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type) +{ + if(type < JZ_NUM_CPUS) + return &cputable[type]; + else + return NULL; +} + +/** \brief Lookup info for a CPU by info string, returns NULL if not found. */ +const jz_cpu_info* jz_get_cpu_info_named(const char* info_str) +{ + for(int i = 0; i < JZ_NUM_CPUS; ++i) + if(!strcmp(cputable[i].info_str, info_str)) + return &cputable[i]; + + return NULL; +} diff --git a/rbutil/jztool/src/fiiom3k.c b/rbutil/jztool/src/fiiom3k.c deleted file mode 100644 index 72e25a1220..0000000000 --- a/rbutil/jztool/src/fiiom3k.c +++ /dev/null @@ -1,274 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * 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.h" -#include "ucl/ucl.h" -#include -#include - -static uint32_t xread32(const uint8_t* d) -{ - uint32_t r = 0; - r |= d[0] << 24; - r |= d[1] << 16; - r |= d[2] << 8; - r |= d[3] << 0; - return r; -} - -/* adapted from firmware/common/ucl_decompress.c */ -static jz_buffer* ucl_unpack(const uint8_t* src, uint32_t src_len, - uint32_t* dst_len) -{ - static const uint8_t magic[8] = - {0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a}; - - jz_buffer* buffer = NULL; - - /* make sure there are enough bytes for the header */ - if(src_len < 18) - goto error; - - /* avoid memcmp for reasons of code size */ - for(size_t i = 0; i < sizeof(magic); ++i) - if(src[i] != magic[i]) - goto error; - - /* read the other header fields */ - /* uint32_t flags = xread32(&src[8]); */ - uint8_t method = src[12]; - /* uint8_t level = src[13]; */ - uint32_t block_size = xread32(&src[14]); - - /* check supported compression method */ - if(method != 0x2e) - goto error; - - /* validate */ - if(block_size < 1024 || block_size > 8*1024*1024) - goto error; - - src += 18; - src_len -= 18; - - /* Calculate amount of space that we might need & allocate a buffer: - * - subtract 4 to account for end of file marker - * - each block is block_size bytes + 8 bytes of header - * - add one to nr_blocks to account for case where file size < block size - * - total size = max uncompressed size of block * nr_blocks - */ - uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1; - uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256); - buffer = jz_buffer_alloc(max_size, NULL); - if(!buffer) - goto error; - - /* perform the decompression */ - uint32_t dst_ilen = buffer->size; - uint8_t* dst = buffer->data; - while(1) { - if(src_len < 4) - goto error; - - uint32_t out_len = xread32(src); src += 4, src_len -= 4; - if(out_len == 0) - break; - - if(src_len < 4) - goto error; - - uint32_t in_len = xread32(src); src += 4, src_len -= 4; - if(in_len > block_size || out_len > block_size || - in_len == 0 || in_len > out_len) - goto error; - - if(src_len < in_len) - goto error; - - if(in_len < out_len) { - uint32_t actual_out_len = dst_ilen; - int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL); - if(rc != UCL_E_OK) - goto error; - if(actual_out_len != out_len) - goto error; - } else { - for(size_t i = 0; i < in_len; ++i) - dst[i] = src[i]; - } - - src += in_len; - src_len -= in_len; - dst += out_len; - dst_ilen -= out_len; - } - - /* subtract leftover number of bytes to get size of compressed output */ - *dst_len = buffer->size - dst_ilen; - return buffer; - - error: - jz_buffer_free(buffer); - return NULL; -} - -static int m3k_stage1(jz_usbdev* dev, jz_buffer* buf) -{ - int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data); - if(rc < 0) - return rc; - - return jz_usb_start1(dev, 0xf4001800); -} - -static int m3k_stage2(jz_usbdev* dev, jz_buffer* buf) -{ - int rc = jz_usb_send(dev, 0x80004000, 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, 0x80004000); -} - -static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file, - bool decompress, jz_buffer** buf) -{ - jz_buffer* buffer = NULL; - mtar_header_t h; - int rc; - - rc = mtar_find(tar, file, &h); - if(rc != MTAR_ESUCCESS) { - jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc); - return JZ_ERR_BAD_FILE_FORMAT; - } - - 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 != MTAR_ESUCCESS) { - 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(decompress) { - uint32_t dst_len; - jz_buffer* nbuf = 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 m3k_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 the FiiO M3K - * \param dev USB device freshly returned by jz_usb_open() - * \param filename Path to the "bootloader.m3k" file - * \return either JZ_SUCCESS or an error code - */ -int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename) -{ - jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL; - mtar_t tar; - int rc; - - rc = mtar_open(&tar, filename, "r"); - 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 = m3k_get_file(dev->jz, &tar, "spl.m3k", false, &spl); - if(rc != JZ_SUCCESS) - goto error; - - rc = m3k_get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader); - if(rc != JZ_SUCCESS) - goto error; - - rc = m3k_get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file); - if(rc != JZ_SUCCESS) - goto error; - - /* Display the version string */ - rc = m3k_show_version(dev->jz, info_file); - if(rc != JZ_SUCCESS) - goto error; - - /* Stage1 boot of SPL to set up hardware */ - rc = m3k_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 = m3k_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; -} diff --git a/rbutil/jztool/src/identify_file.c b/rbutil/jztool/src/identify_file.c index e735075687..e475d98a3b 100644 --- a/rbutil/jztool/src/identify_file.c +++ b/rbutil/jztool/src/identify_file.c @@ -118,43 +118,52 @@ int jz_identify_x1000_spl(const void* data, size_t len) static const struct scramble_model_info { const char* name; int model_num; + size_t offset_crc; + size_t offset_name; + size_t offset_data; } scramble_models[] = { - {"fiio", 114}, - {NULL, 0}, + {"fiio", 114, 0, 4, 8}, + {"shq1", 115, 0, 4, 8}, + {"eros", 116, 0, 4, 8}, + {NULL, 0, 0, 0, 0}, }; /** \brief Identify a file as a Rockbox `scramble` image * \param data File data buffer * \param len Length of file * \return JZ_SUCCESS if file looks correct, or one of the following errors - * \retval JZ_IDERR_WRONG_SIZE file too small to be valid * \retval JZ_IDERR_UNRECOGNIZED_MODEL unsupported/unknown model type * \retval JZ_IDERR_BAD_CHECKSUM checksum mismatch */ int jz_identify_scramble_image(const void* data, size_t len) { - /* 4 bytes checksum + 4 bytes player model */ - if(len < 8) - return JZ_IDERR_WRONG_SIZE; + const uint8_t* dat; + const struct scramble_model_info* model_info; + uint32_t sum, file_sum; + + dat = (const uint8_t*)data; + model_info = &scramble_models[0]; /* Look up the model number */ - const uint8_t* dat = (const uint8_t*)data; - const struct scramble_model_info* model_info = &scramble_models[0]; - for(; model_info->name != NULL; ++model_info) - if(!memcmp(&dat[4], model_info->name, 4)) + for(; model_info->name != NULL; ++model_info) { + if(model_info->offset_name + 4 > len) + continue; + if(!memcmp(&dat[model_info->offset_name], model_info->name, 4)) break; + } if(model_info->name == NULL) return JZ_IDERR_UNRECOGNIZED_MODEL; /* Compute the checksum */ - uint32_t sum = model_info->model_num; - for(size_t i = 8; i < len; ++i) + sum = model_info->model_num; + for(size_t i = model_info->offset_data; i < len; ++i) sum += dat[i]; /* Compare with file's checksum, it's stored in big-endian form */ - uint32_t fsum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3]; - if(sum != fsum) + dat += model_info->offset_crc; + file_sum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3]; + if(sum != file_sum) return JZ_IDERR_BAD_CHECKSUM; return JZ_SUCCESS; diff --git a/rbutil/jztool/src/ucl_unpack.c b/rbutil/jztool/src/ucl_unpack.c new file mode 100644 index 0000000000..3b199c7008 --- /dev/null +++ b/rbutil/jztool/src/ucl_unpack.c @@ -0,0 +1,128 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "ucl/ucl.h" + +static uint32_t xread32(const uint8_t* d) +{ + uint32_t r = 0; + r |= d[0] << 24; + r |= d[1] << 16; + r |= d[2] << 8; + r |= d[3] << 0; + return r; +} + +/* adapted from firmware/common/ucl_decompress.c */ +jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len) +{ + static const uint8_t magic[8] = + {0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a}; + + jz_buffer* buffer = NULL; + + /* make sure there are enough bytes for the header */ + if(src_len < 18) + goto error; + + /* avoid memcmp for reasons of code size */ + for(size_t i = 0; i < sizeof(magic); ++i) + if(src[i] != magic[i]) + goto error; + + /* read the other header fields */ + /* uint32_t flags = xread32(&src[8]); */ + uint8_t method = src[12]; + /* uint8_t level = src[13]; */ + uint32_t block_size = xread32(&src[14]); + + /* check supported compression method */ + if(method != 0x2e) + goto error; + + /* validate */ + if(block_size < 1024 || block_size > 8*1024*1024) + goto error; + + src += 18; + src_len -= 18; + + /* Calculate amount of space that we might need & allocate a buffer: + * - subtract 4 to account for end of file marker + * - each block is block_size bytes + 8 bytes of header + * - add one to nr_blocks to account for case where file size < block size + * - total size = max uncompressed size of block * nr_blocks + */ + uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1; + uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256); + buffer = jz_buffer_alloc(max_size, NULL); + if(!buffer) + goto error; + + /* perform the decompression */ + uint32_t dst_ilen = buffer->size; + uint8_t* dst = buffer->data; + while(1) { + if(src_len < 4) + goto error; + + uint32_t out_len = xread32(src); src += 4, src_len -= 4; + if(out_len == 0) + break; + + if(src_len < 4) + goto error; + + uint32_t in_len = xread32(src); src += 4, src_len -= 4; + if(in_len > block_size || out_len > block_size || + in_len == 0 || in_len > out_len) + goto error; + + if(src_len < in_len) + goto error; + + if(in_len < out_len) { + uint32_t actual_out_len = dst_ilen; + int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL); + if(rc != UCL_E_OK) + goto error; + if(actual_out_len != out_len) + goto error; + } else { + for(size_t i = 0; i < in_len; ++i) + dst[i] = src[i]; + } + + src += in_len; + src_len -= in_len; + dst += out_len; + dst_ilen -= out_len; + } + + /* subtract leftover number of bytes to get size of compressed output */ + *dst_len = buffer->size - dst_ilen; + return buffer; + + error: + jz_buffer_free(buffer); + return NULL; +} diff --git a/rbutil/jztool/src/usb.c b/rbutil/jztool/src/usb.c index c101f2be77..cfc3ba60cb 100644 --- a/rbutil/jztool/src/usb.c +++ b/rbutil/jztool/src/usb.c @@ -22,6 +22,7 @@ #include "jztool_private.h" #include #include +#include #define VR_GET_CPU_INFO 0 #define VR_SET_DATA_ADDRESS 1 @@ -145,11 +146,12 @@ void jz_usb_close(jz_usbdev* dev) // Does an Ingenic-specific vendor request // Written with X1000 in mind but other Ingenic CPUs have the same commands -static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg) +static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg, + void* buffer, int buflen) { int rc = libusb_control_transfer(dev->handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, - req, arg >> 16, arg & 0xffff, NULL, 0, 1000); + req, arg >> 16, arg & 0xffff, buffer, buflen, 1000); if(rc < 0) { jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc)); @@ -200,11 +202,11 @@ static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr, size_t len, void* data) { int rc; - rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr); + rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr, NULL, 0); if(rc < 0) return rc; - rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len); + rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0); if(rc < 0) return rc; @@ -242,7 +244,7 @@ int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data) */ int jz_usb_start1(jz_usbdev* dev, uint32_t addr) { - return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr); + return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0); } /** \brief Execute stage2 program jumping to the specified address @@ -252,7 +254,7 @@ int jz_usb_start1(jz_usbdev* dev, uint32_t addr) */ int jz_usb_start2(jz_usbdev* dev, uint32_t addr) { - return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr); + return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0); } /** \brief Ask device to flush CPU caches @@ -261,5 +263,29 @@ int jz_usb_start2(jz_usbdev* dev, uint32_t addr) */ int jz_usb_flush_caches(jz_usbdev* dev) { - return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0); + return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0); +} + +/** \brief Ask device for CPU info string + * \param dev USB device + * \param buffer Buffer to hold the info string + * \param buflen Size of the buffer, in bytes + * \return either JZ_SUCCESS on success or a failure code + * + * The buffer will always be null terminated, but to ensure the info string is + * not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long. + */ +int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen) +{ + char tmpbuf[JZ_CPUINFO_BUFLEN]; + int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8); + if(rc != JZ_SUCCESS) + return rc; + + if(buflen > 0) { + strncpy(buffer, tmpbuf, buflen); + buffer[buflen - 1] = 0; + } + + return JZ_SUCCESS; } diff --git a/rbutil/jztool/src/x1000.c b/rbutil/jztool/src/x1000.c new file mode 100644 index 0000000000..aacad0ef01 --- /dev/null +++ b/rbutil/jztool/src/x1000.c @@ -0,0 +1,179 @@ +/*************************************************************************** + * __________ __ ___. + * 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.h" +#include +#include + +/* TODO: these functions could be refactored to be CPU-agnostic */ +static int run_stage1(jz_usbdev* dev, jz_buffer* buf) +{ + int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data); + if(rc < 0) + return rc; + + return jz_usb_start1(dev, 0xf4001800); +} + +static int run_stage2(jz_usbdev* dev, jz_buffer* buf) +{ + int rc = jz_usb_send(dev, 0x80004000, 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, 0x80004000); +} + +static int get_file(jz_context* jz, mtar_t* tar, const char* file, + bool decompress, jz_buffer** buf) +{ + jz_buffer* buffer = NULL; + mtar_header_t h; + int rc; + + rc = mtar_find(tar, file, &h); + if(rc != MTAR_ESUCCESS) { + jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc); + return JZ_ERR_BAD_FILE_FORMAT; + } + + 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 != MTAR_ESUCCESS) { + 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(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, "r"); + 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; + + rc = get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader); + if(rc != JZ_SUCCESS) + 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; +} -- cgit v1.2.3