From b41d53792c4c4e4abd6d810b2765d865775f5104 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 13 Apr 2021 16:58:15 +0100 Subject: jztool: New utility for installing a bootloader on FiiO M3K At present, this is just a command line tool for Linux only. Integrating this with the Rockbox utility and porting to other platforms should be straightforward; the README contains more information. Change-Id: Ie66fc837a02ab13c878925360cabc9805597548a --- .gitignore | 1 + rbutil/jztool/Makefile | 37 +++++ rbutil/jztool/README.md | 60 ++++++++ rbutil/jztool/include/jztool.h | 200 ++++++++++++++++++++++++++ rbutil/jztool/jztool.c | 227 +++++++++++++++++++++++++++++ rbutil/jztool/src/buffer.c | 134 ++++++++++++++++++ rbutil/jztool/src/context.c | 167 ++++++++++++++++++++++ rbutil/jztool/src/device_info.c | 98 +++++++++++++ rbutil/jztool/src/fiiom3k.c | 283 +++++++++++++++++++++++++++++++++++++ rbutil/jztool/src/identify_file.c | 179 +++++++++++++++++++++++ rbutil/jztool/src/jztool_private.h | 44 ++++++ rbutil/jztool/src/paramlist.c | 135 ++++++++++++++++++ rbutil/jztool/src/usb.c | 203 ++++++++++++++++++++++++++ rbutil/jztool/src/x1000.c | 193 +++++++++++++++++++++++++ 14 files changed, 1961 insertions(+) create mode 100644 rbutil/jztool/Makefile create mode 100644 rbutil/jztool/README.md create mode 100644 rbutil/jztool/include/jztool.h create mode 100644 rbutil/jztool/jztool.c create mode 100644 rbutil/jztool/src/buffer.c create mode 100644 rbutil/jztool/src/context.c create mode 100644 rbutil/jztool/src/device_info.c create mode 100644 rbutil/jztool/src/fiiom3k.c create mode 100644 rbutil/jztool/src/identify_file.c create mode 100644 rbutil/jztool/src/jztool_private.h create mode 100644 rbutil/jztool/src/paramlist.c create mode 100644 rbutil/jztool/src/usb.c create mode 100644 rbutil/jztool/src/x1000.c diff --git a/.gitignore b/.gitignore index 6e22d4dcf8..93253775a8 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,7 @@ __pycache__ /rbutil/mknwzboot/mknwzboot /rbutil/mkzenboot/mkzenboot /rbutil/sansapatcher/sansapatcher +/rbutil/jztool/jztool /rbutil/tools/bin2c # /tools/ diff --git a/rbutil/jztool/Makefile b/rbutil/jztool/Makefile new file mode 100644 index 0000000000..aa434adf1f --- /dev/null +++ b/rbutil/jztool/Makefile @@ -0,0 +1,37 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ + +CFLAGS += -Wall -Wextra -Iinclude +OUTPUT = jztool + +ifdef RELEASE +CFLAGS += -Os -DNDEBUG +else +CFLAGS += -O0 -ggdb +endif + +LIBSOURCES := src/buffer.c src/context.c src/device_info.c \ + src/fiiom3k.c src/identify_file.c src/paramlist.c \ + src/usb.c src/x1000.c +SOURCES := $(LIBSOURCES) jztool.c +EXTRADEPS := + +CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -) + +ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32) +# TODO: support Windows +else +ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE) +# TODO: support OSX +else +# Linux +CFLAGS += `pkg-config --cflags libusb-1.0` +LDOPTS += `pkg-config --libs libusb-1.0` +endif +endif + +include ../libtools.make diff --git a/rbutil/jztool/README.md b/rbutil/jztool/README.md new file mode 100644 index 0000000000..6a9b78f8d7 --- /dev/null +++ b/rbutil/jztool/README.md @@ -0,0 +1,60 @@ +# jztool -- Ingenic device utility & bootloader installer + +The `jztool` utility can install, backup, and restore the bootloader on +Rockbox players based on a supported Ingenic SoC. + +## FiiO M3K + +To use `jztool` on the FiiO M3K you have to connect the player to your +computer in USB boot mode. + +The easiest way to do this is by plugging in the microUSB cable to the M3K +and holding the volume down button while plugging the USB into your computer. +If you entered USB boot mode, the button light will turn on but the LCD will +turn off. + +To install or update the Rockbox bootloader on the M3K, use the command +`jztool fiiom3k install`. It is recommended that you take a backup of your +current bootloader so you can restore it in case of any problems. + +After any operation finishes, you will have to force a power off of the M3K +by holding down the power button for at least 10 seconds. This must be done +whether the operation succeeds or fails. Just don't power off or unplug the +device in the middle of an operation -- that might make bad things happen. + +See `jztool --help` for info. + +## TODO list + +### Add better documentation and logging + +There's only a bare minimum of documentation, and logging is sparse, not +really enough to debug problems. + +Some of the error messages could be friendlier too. + +### Integration with the Rockbox utility + +Adding support to the Rockbox utility should be mostly boilerplate since the +jztool library wraps all the troublesome details. + +Getting appropriate privileges to access the USB device is the main issue. +Preferably, the Rockbox utility should not run as root/admin/etc. + +- Windows: not sure +- Linux: needs udev rules or root privileges +- Mac: apparently does not need privileges + +### Porting to Windows + +Windows wants to see a driver installed before we can access the USB device, +the easiest way to do this is by having the user run Zadig, a 3rd party app +which can install the WinUSB driver. WinUSB itself is from Microsoft and +bundled with Windows. + +Zadig's homepage: https://zadig.akeo.ie/ + +### Porting to Mac + +According to the libusb wiki, libusb works on Mac without any special setup or +privileges, presumably porting there is easy. diff --git a/rbutil/jztool/include/jztool.h b/rbutil/jztool/include/jztool.h new file mode 100644 index 0000000000..e16bc9765f --- /dev/null +++ b/rbutil/jztool/include/jztool.h @@ -0,0 +1,200 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + * + ****************************************************************************/ + +#ifndef JZTOOL_H +#define JZTOOL_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * Types, enumerations, etc + */ + +typedef struct jz_context jz_context; +typedef struct jz_usbdev jz_usbdev; +typedef struct jz_device_info jz_device_info; +typedef struct jz_buffer jz_buffer; +typedef struct jz_paramlist jz_paramlist; + +typedef enum jz_error jz_error; +typedef enum jz_identify_error jz_identify_error; +typedef enum jz_log_level jz_log_level; +typedef enum jz_device_type jz_device_type; +typedef enum jz_cpu_type jz_cpu_type; + +typedef void(*jz_log_cb)(jz_log_level, const char*); +typedef int(*jz_device_action_fn)(jz_context*, jz_paramlist*); + +enum jz_error { + JZ_SUCCESS = 0, + JZ_ERR_OUT_OF_MEMORY = -1, + JZ_ERR_OPEN_FILE = -2, + JZ_ERR_FILE_IO = -3, + JZ_ERR_USB = -4, + JZ_ERR_NO_DEVICE = -5, + JZ_ERR_BAD_FILE_FORMAT = -6, + JZ_ERR_FLASH_ERROR = -7, + JZ_ERR_OTHER = -99, +}; + +enum jz_identify_error { + JZ_IDERR_OTHER = -1, + JZ_IDERR_WRONG_SIZE = -2, + JZ_IDERR_BAD_HEADER = -3, + JZ_IDERR_BAD_CHECKSUM = -4, + JZ_IDERR_UNRECOGNIZED_MODEL = -5, +}; + +enum jz_log_level { + JZ_LOG_IGNORE = -1, + JZ_LOG_ERROR = 0, + JZ_LOG_WARNING = 1, + JZ_LOG_NOTICE = 2, + JZ_LOG_DETAIL = 3, + JZ_LOG_DEBUG = 4, +}; + +enum jz_device_type { + JZ_DEVICE_FIIOM3K, +}; + +enum jz_cpu_type { + JZ_CPU_X1000, +}; + +struct jz_device_info { + const char* name; + const char* description; + jz_device_type device_type; + jz_cpu_type cpu_type; + uint16_t vendor_id; + uint16_t product_id; + int num_actions; + const char* const* action_names; + const jz_device_action_fn* action_funcs; + const char* const* const* action_params; +}; + +struct jz_buffer { + size_t size; + uint8_t* data; +}; + +/****************************************************************************** + * Library context and general functions + */ + +jz_context* jz_context_create(void); +void jz_context_destroy(jz_context* jz); + +void jz_context_set_user_data(jz_context* jz, void* ptr); +void* jz_context_get_user_data(jz_context* jz); + +void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb); +void jz_context_set_log_level(jz_context* jz, jz_log_level lev); + +void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...); +void jz_log_cb_stderr(jz_log_level lev, const char* msg); + +void jz_sleepms(int ms); + +/****************************************************************************** + * Device and file info + */ + +int jz_get_num_device_info(void); +const jz_device_info* jz_get_device_info(jz_device_type type); +const jz_device_info* jz_get_device_info_named(const char* name); +const jz_device_info* jz_get_device_info_indexed(int index); + +int jz_identify_x1000_spl(const void* data, size_t len); +int jz_identify_scramble_image(const void* data, size_t len); +int jz_identify_fiiom3k_bootimage(const void* data, size_t len); + +/****************************************************************************** + * USB boot ROM protocol + */ + +int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id); +void jz_usb_close(jz_usbdev* dev); + +int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data); +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); +int jz_usb_start2(jz_usbdev* dev, uint32_t addr); +int jz_usb_flush_caches(jz_usbdev* dev); + +/****************************************************************************** + * X1000 flash protocol + */ + +int jz_x1000_setup(jz_usbdev* dev, size_t spl_len, const void* spl_data); +int jz_x1000_read_flash(jz_usbdev* dev, uint32_t addr, size_t len, void* data); +int jz_x1000_write_flash(jz_usbdev* dev, uint32_t addr, size_t len, const void* data); +int jz_x1000_boot_rockbox(jz_usbdev* dev); + +/****************************************************************************** + * FiiO M3K bootloader backup/installation + */ + +int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr); +int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf); +int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size, + const void* spl_buf, size_t spl_size, + const void* boot_buf, size_t boot_size); + +int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl); +int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl); +int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl); + +/****************************************************************************** + * Simple buffer API + */ + +jz_buffer* jz_buffer_alloc(size_t size, const void* data); +void jz_buffer_free(jz_buffer* buf); + +int jz_buffer_load(jz_buffer** buf, const char* filename); +int jz_buffer_save(jz_buffer* buf, const char* filename); + +/****************************************************************************** + * Parameter list + */ + +jz_paramlist* jz_paramlist_new(void); +void jz_paramlist_free(jz_paramlist* pl); +int jz_paramlist_set(jz_paramlist* pl, const char* param, const char* value); +const char* jz_paramlist_get(jz_paramlist* pl, const char* param); + +/****************************************************************************** + * END + */ + +#ifdef __cplusplus +} +#endif + +#endif /* JZTOOL_H */ diff --git a/rbutil/jztool/jztool.c b/rbutil/jztool/jztool.c new file mode 100644 index 0000000000..5aae8d7457 --- /dev/null +++ b/rbutil/jztool/jztool.c @@ -0,0 +1,227 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include +#include + +jz_context* jz = NULL; +const jz_device_info* dev_info = NULL; +int dev_action = -1; +jz_paramlist* action_params = NULL; + +void usage(void) +{ + printf("Usage:\n" + " jztool [global options] [action options]\n" + "\n" + "Global options:\n" + "\n" + " -h, --help Display this help\n" + " -q, --quiet Don't log anything except errors\n" + " -v, --verbose Display detailed logging output\n" + " -l, --loglevel LEVEL Set log level\n"); + + printf("Supported devices:\n\n"); + int n = jz_get_num_device_info(); + for(int i = 0; i < n; ++i) { + const jz_device_info* info = jz_get_device_info_indexed(i); + printf(" %s - %s\n", info->name, info->description); + } + + printf( +"\n" +"Available actions for fiiom3k:\n" +"\n" +" install --spl --bootloader \n" +" [--without-backup yes] [--backup IMAGE]\n" +" Install or update the Rockbox bootloader on a device.\n" +"\n" +" If --backup is given, back up the current bootloader to IMAGE before\n" +" installing the new bootloader. The installer will normally refuse to\n" +" overwrite your current bootloader; pass '--without-backup yes' if you\n" +" really want to proceed without taking a backup.\n" +"\n" +" WARNING: it is NOT RECOMMENDED to install the Rockbox bootloader\n" +" without taking a backup of the original firmware bootloader. It may\n" +" be very difficult or impossible to recover your player without one.\n" +" At least one M3Ks is known to not to work with the Rockbox bootloader,\n" +" so it is very important to take a backup.\n" +"\n" +" backup --image IMAGE\n" +" Backup the current bootloader to the file IMAGE\n" +"\n" +" restore --image IMAGE\n" +" Restore a bootloader image backup from the file IMAGE\n" +"\n"); + + exit(4); +} + +void cleanup(void) +{ + if(action_params) + jz_paramlist_free(action_params); + if(jz) + jz_context_destroy(jz); +} + +int main(int argc, char** argv) +{ + if(argc < 2) + usage(); + + /* Library initialization */ + jz = jz_context_create(); + if(!jz) { + fprintf(stderr, "ERROR: Can't create context"); + return 1; + } + + atexit(cleanup); + jz_context_set_log_cb(jz, jz_log_cb_stderr); + jz_context_set_log_level(jz, JZ_LOG_NOTICE); + + /* Parse global options */ + --argc, ++argv; + while(argc > 0 && argv[0][0] == '-') { + if(!strcmp(*argv, "-h") || !strcmp(*argv, "--help")) + usage(); + else if(!strcmp(*argv, "-q") || !strcmp(*argv, "--quiet")) + jz_context_set_log_level(jz, JZ_LOG_ERROR); + else if(!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose")) + jz_context_set_log_level(jz, JZ_LOG_DETAIL); + else if(!strcmp(*argv, "-l") || !strcmp(*argv, "--loglevel")) { + ++argv; + if(--argc == 0) { + jz_log(jz, JZ_LOG_ERROR, "Missing argument to option %s", *argv); + exit(2); + } + + enum jz_log_level level; + if(!strcmp(*argv, "ignore")) + level = JZ_LOG_IGNORE; + else if(!strcmp(*argv, "error")) + level = JZ_LOG_ERROR; + else if(!strcmp(*argv, "warning")) + level = JZ_LOG_WARNING; + else if(!strcmp(*argv, "notice")) + level = JZ_LOG_NOTICE; + else if(!strcmp(*argv, "detail")) + level = JZ_LOG_DETAIL; + else if(!strcmp(*argv, "debug")) + level = JZ_LOG_DEBUG; + else { + jz_log(jz, JZ_LOG_ERROR, "Invalid log level '%s'", *argv); + exit(2); + } + + jz_context_set_log_level(jz, level); + } else { + jz_log(jz, JZ_LOG_ERROR, "Invalid global option '%s'", *argv); + exit(2); + } + + --argc, ++argv; + } + + /* Read the device type */ + if(argc == 0) { + jz_log(jz, JZ_LOG_ERROR, "No device specified (try jztool --help)"); + exit(2); + } + + dev_info = jz_get_device_info_named(*argv); + if(!dev_info) { + jz_log(jz, JZ_LOG_ERROR, "Unknown device '%s' (try jztool --help)", *argv); + exit(2); + } + + /* Read the action */ + --argc, ++argv; + if(argc == 0) { + jz_log(jz, JZ_LOG_ERROR, "No action specified (try jztool --help)"); + exit(2); + } + + for(dev_action = 0; dev_action < dev_info->num_actions; ++dev_action) + if(!strcmp(*argv, dev_info->action_names[dev_action])) + break; + + if(dev_action == dev_info->num_actions) { + jz_log(jz, JZ_LOG_ERROR, "Unknown action '%s' (try jztool --help)", *argv); + exit(2); + } + + /* Parse the action options */ + action_params = jz_paramlist_new(); + if(!action_params) { + jz_log(jz, JZ_LOG_ERROR, "Out of memory: can't create paramlist"); + exit(1); + } + + const char* const* allowed_params = dev_info->action_params[dev_action]; + + --argc, ++argv; + while(argc > 0 && argv[0][0] == '-') { + if(argv[0][1] != '-') { + jz_log(jz, JZ_LOG_ERROR, "Invalid option '%s' for action", *argv); + exit(2); + } + + bool bad_option = true; + for(int i = 0; allowed_params[i] != NULL; ++i) { + if(!strcmp(&argv[0][2], allowed_params[i])) { + ++argv; + if(--argc == 0) { + jz_log(jz, JZ_LOG_ERROR, "Missing argument for parameter '%s'", *argv); + exit(2); + } + + int rc = jz_paramlist_set(action_params, allowed_params[i], *argv); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Out of memory"); + exit(1); + } + + bad_option = false; + } + } + + if(bad_option) { + jz_log(jz, JZ_LOG_ERROR, "Invalid option '%s' for action", *argv); + exit(2); + } + + --argc, ++argv; + } + + if(argc != 0) { + jz_log(jz, JZ_LOG_ERROR, "Excess arguments on command line"); + exit(2); + } + + /* Invoke action handler */ + int rc = dev_info->action_funcs[dev_action](jz, action_params); + return (rc < 0) ? 1 : 0; +} diff --git a/rbutil/jztool/src/buffer.c b/rbutil/jztool/src/buffer.c new file mode 100644 index 0000000000..9e9c9ff5d1 --- /dev/null +++ b/rbutil/jztool/src/buffer.c @@ -0,0 +1,134 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include + +/** \brief Allocate a buffer, optionally providing its contents. + * \param size Number of bytes to allocate + * \param data Initial contents of the buffer, must be at least `size` bytes + * \return Pointer to buffer or NULL if out of memory. + * \note The buffer will not take ownership of the `data` pointer, instead it + * allocates a fresh buffer and copies the contents of `data` into it. + */ +jz_buffer* jz_buffer_alloc(size_t size, const void* data) +{ + jz_buffer* buf = malloc(sizeof(struct jz_buffer)); + if(!buf) + return NULL; + + buf->data = malloc(size); + if(!buf->data) { + free(buf); + return NULL; + } + + if(data) + memcpy(buf->data, data, size); + + buf->size = size; + return buf; +} + +/** \brief Free a buffer + */ +void jz_buffer_free(jz_buffer* buf) +{ + if(buf) { + free(buf->data); + free(buf); + } +} + +/** \brief Load a buffer from a file + * \param buf Returns loaded buffer on success, unmodified on error + * \param filename Path to the file + * \return either JZ_SUCCESS, or one of the following errors + * \retval JZ_ERR_OPEN_FILE file cannot be opened + * \retval JZ_ERR_OUT_OF_MEMORY cannot allocate buffer to hold file contents + * \retval JZ_ERR_FILE_IO problem reading file data + */ +int jz_buffer_load(jz_buffer** buf, const char* filename) +{ + FILE* f; + jz_buffer* b; + int rc; + + f = fopen(filename, "rb"); + if(!f) + return JZ_ERR_OPEN_FILE; + + fseek(f, 0, SEEK_END); + int size = ftell(f); + fseek(f, 0, SEEK_SET); + + b = jz_buffer_alloc(size, NULL); + if(!b) { + rc = JZ_ERR_OUT_OF_MEMORY; + goto err_fclose; + } + + if(fread(b->data, size, 1, f) != 1) { + rc = JZ_ERR_FILE_IO; + goto err_free_buf; + } + + rc = JZ_SUCCESS; + *buf = b; + + err_fclose: + fclose(f); + return rc; + + err_free_buf: + jz_buffer_free(b); + goto err_fclose; +} + +/** \brief Save a buffer to a file + * \param buf Buffer to be written out + * \param filename Path to the file + * \return either JZ_SUCCESS, or one of the following errors + * \retval JZ_ERR_OPEN_FILE file cannot be opened + * \retval JZ_ERR_FILE_IO problem writing file data + */ +int jz_buffer_save(jz_buffer* buf, const char* filename) +{ + int rc; + FILE* f; + + f = fopen(filename, "wb"); + if(!f) + return JZ_ERR_OPEN_FILE; + + if(fwrite(buf->data, buf->size, 1, f) != 1) { + rc = JZ_ERR_FILE_IO; + goto err_fclose; + } + + rc = JZ_SUCCESS; + + err_fclose: + fclose(f); + return rc; +} diff --git a/rbutil/jztool/src/context.c b/rbutil/jztool/src/context.c new file mode 100644 index 0000000000..94b21b5196 --- /dev/null +++ b/rbutil/jztool/src/context.c @@ -0,0 +1,167 @@ +/*************************************************************************** + * __________ __ ___. + * 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_private.h" +#include +#include +#include +#include +#include +#include + +/** \brief Allocate a library context + * \returns New context or NULL if out of memory + */ +jz_context* jz_context_create(void) +{ + jz_context* jz = malloc(sizeof(struct jz_context)); + if(!jz) + return NULL; + + memset(jz, 0, sizeof(struct jz_context)); + jz->log_level = JZ_LOG_ERROR; + return jz; +} + +/** \brief Destroy the context and free its memory */ +void jz_context_destroy(jz_context* jz) +{ + if(jz->usb_ctx) { + jz_log(jz, JZ_LOG_ERROR, "BUG: USB was not cleaned up properly"); + libusb_exit(jz->usb_ctx); + } + + free(jz); +} + +/** \brief Set a user data pointer. Useful for callbacks. */ +void jz_context_set_user_data(jz_context* jz, void* ptr) +{ + jz->user_data = ptr; +} + +/** \brief Get the user data pointer */ +void* jz_context_get_user_data(jz_context* jz) +{ + return jz->user_data; +} + +/** \brief Set the log message callback. + * \note By default, no message callback is set! No messages will be logged + * in this case, so ensure you set a callback if messages are desired. + */ +void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb) +{ + jz->log_cb = cb; +} + +/** \brief Set the log level. + * + * Messages of less importance than the set log level are not logged. + * The default log level is `JZ_LOG_WARNING`. The special log level + * `JZ_LOG_IGNORE` can be used to disable all logging temporarily. + * + * The `JZ_LOG_DEBUG` log level is extremely verbose and will log all calls, + * normally it's only useful for catching bugs. + */ +void jz_context_set_log_level(jz_context* jz, jz_log_level lev) +{ + jz->log_level = lev; +} + +/** \brief Log an informational message. + * \param lev Log level for this message + * \param fmt `printf` style message format string + */ +void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...) +{ + if(!jz->log_cb) + return; + if(lev == JZ_LOG_IGNORE) + return; + if(lev > jz->log_level) + return; + + va_list ap; + + va_start(ap, fmt); + int n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + if(n < 0) + return; + + char* buf = malloc(n + 1); + if(!buf) + return; + + va_start(ap, fmt); + n = vsnprintf(buf, n + 1, fmt, ap); + va_end(ap); + + if(n >= 0) + jz->log_cb(lev, buf); + + free(buf); +} + +/** \brief Log callback which writes messages to `stderr`. + */ +void jz_log_cb_stderr(jz_log_level lev, const char* msg) +{ + static const char* const tags[] = + {"ERROR", "WARNING", "NOTICE", "DETAIL", "DEBUG"}; + fprintf(stderr, "[%7s] %s\n", tags[lev], msg); + fflush(stderr); +} + +/** \brief Sleep for `ms` milliseconds. + */ +void jz_sleepms(int ms) +{ + struct timespec ts; + long ns = ms % 1000; + ts.tv_nsec = ns * 1000 * 1000; + ts.tv_sec = ms / 1000; + nanosleep(&ts, NULL); +} + +int jz_context_ref_libusb(jz_context* jz) +{ + if(jz->usb_ctxref == 0) { + int rc = libusb_init(&jz->usb_ctx); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "libusb_init: %s", libusb_strerror(rc)); + return JZ_ERR_USB; + } + } + + jz->usb_ctxref += 1; + return JZ_SUCCESS; +} + +void jz_context_unref_libusb(jz_context* jz) +{ + if(--jz->usb_ctxref == 0) { + libusb_exit(jz->usb_ctx); + jz->usb_ctx = NULL; + } +} diff --git a/rbutil/jztool/src/device_info.c b/rbutil/jztool/src/device_info.c new file mode 100644 index 0000000000..bc1477be32 --- /dev/null +++ b/rbutil/jztool/src/device_info.c @@ -0,0 +1,98 @@ +/*************************************************************************** + * __________ __ ___. + * 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 + +static const char* const fiiom3k_action_names[] = { + "install", + "backup", + "restore", +}; + +static const char* const fiiom3k_install_action_params[] = + {"spl", "bootloader", "backup", "without-backup", NULL}; + +static const char* const fiiom3k_backuprestore_action_params[] = + {"spl", "image", NULL}; + +static const char* const* fiiom3k_action_params[] = { + fiiom3k_install_action_params, + fiiom3k_backuprestore_action_params, + fiiom3k_backuprestore_action_params, +}; + +static const jz_device_action_fn fiiom3k_action_funcs[] = { + jz_fiiom3k_install, + jz_fiiom3k_backup, + jz_fiiom3k_restore, +}; + +static const jz_device_info infotable[] = { + { + .name = "fiiom3k", + .description = "FiiO M3K", + .device_type = JZ_DEVICE_FIIOM3K, + .cpu_type = JZ_CPU_X1000, + .vendor_id = 0xa108, + .product_id = 0x1000, + .num_actions = sizeof(fiiom3k_action_names)/sizeof(void*), + .action_names = fiiom3k_action_names, + .action_funcs = fiiom3k_action_funcs, + .action_params = fiiom3k_action_params, + }, +}; + +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; +} + +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; +} + +/** \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) + if(!strcmp(infotable[i].name, name)) + return &infotable[i]; + + return NULL; +} + +/** \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) + return &infotable[index]; + else + return NULL; +} diff --git a/rbutil/jztool/src/fiiom3k.c b/rbutil/jztool/src/fiiom3k.c new file mode 100644 index 0000000000..a43863c2f7 --- /dev/null +++ b/rbutil/jztool/src/fiiom3k.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * __________ __ ___. + * 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 + +#define IMAGE_ADDR 0 +#define IMAGE_SIZE (128 * 1024) +#define SPL_OFFSET 0 +#define SPL_SIZE (12 * 1024) +#define BOOT_OFFSET (26 * 1024) +#define BOOT_SIZE (102 * 1024) + +int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr) +{ + jz_buffer* buf = jz_buffer_alloc(IMAGE_SIZE, NULL); + if(!buf) + return JZ_ERR_OUT_OF_MEMORY; + + int rc = jz_x1000_read_flash(dev, IMAGE_ADDR, buf->size, buf->data); + if(rc < 0) { + jz_buffer_free(buf); + return rc; + } + + *bufptr = buf; + return JZ_SUCCESS; +} + +int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf) +{ + int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size); + if(rc < 0 || image_size != IMAGE_SIZE) + return JZ_ERR_BAD_FILE_FORMAT; + + rc = jz_x1000_write_flash(dev, IMAGE_ADDR, image_size, image_buf); + if(rc < 0) + return rc; + + return JZ_SUCCESS; +} + +int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size, + const void* spl_buf, size_t spl_size, + const void* boot_buf, size_t boot_size) +{ + int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Boot image is invalid: %d", rc); + return JZ_ERR_BAD_FILE_FORMAT; + } + + rc = jz_identify_x1000_spl(spl_buf, spl_size); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "SPL image is invalid: %d", rc); + return JZ_ERR_BAD_FILE_FORMAT; + } + + if(spl_size > SPL_SIZE) { + jz_log(jz, JZ_LOG_ERROR, "SPL is too big"); + return JZ_ERR_BAD_FILE_FORMAT; + } + + rc = jz_identify_scramble_image(boot_buf, boot_size); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Bootloader image is invalid: %d", rc); + return JZ_ERR_BAD_FILE_FORMAT; + } + + if(boot_size > BOOT_SIZE) { + jz_log(jz, JZ_LOG_ERROR, "Bootloader is too big"); + return JZ_ERR_BAD_FILE_FORMAT; + } + + uint8_t* imgdat = (uint8_t*)image_buf; + memset(&imgdat[SPL_OFFSET], 0xff, SPL_SIZE); + memcpy(&imgdat[SPL_OFFSET], spl_buf, spl_size); + memset(&imgdat[BOOT_OFFSET], 0xff, BOOT_SIZE); + memcpy(&imgdat[BOOT_OFFSET], boot_buf, boot_size); + return JZ_SUCCESS; +} + +#define IMGBUF 0 +#define SPLBUF 1 +#define BOOTBUF 2 +#define NUMBUFS 3 +#define IMGBUF_NAME "image" +#define SPLBUF_NAME "spl" +#define BOOTBUF_NAME "bootloader" +#define FIIOM3K_INIT_WORKSTATE {0} + +struct fiiom3k_workstate { + jz_usbdev* dev; + jz_buffer* bufs[NUMBUFS]; +}; + +static void fiiom3k_action_cleanup(struct fiiom3k_workstate* state) +{ + for(int i = 0; i < NUMBUFS; ++i) + if(state->bufs[i]) + jz_buffer_free(state->bufs[i]); + + if(state->dev) + jz_usb_close(state->dev); +} + +static int fiiom3k_action_loadbuf(jz_context* jz, jz_paramlist* pl, + struct fiiom3k_workstate* state, int idx) +{ + const char* const paramnames[] = {IMGBUF_NAME, SPLBUF_NAME, BOOTBUF_NAME}; + + if(state->bufs[idx]) + return JZ_SUCCESS; + + const char* filename = jz_paramlist_get(pl, paramnames[idx]); + if(!filename) { + jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", paramnames[idx]); + return JZ_ERR_OTHER; + } + + int rc = jz_buffer_load(&state->bufs[idx], filename); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Error reading '%s' file (%d): %s", paramnames[idx], rc, filename); + return rc; + } + + return JZ_SUCCESS; +} + +static int fiiom3k_action_setup(jz_context* jz, jz_paramlist* pl, + struct fiiom3k_workstate* state) +{ + const jz_device_info* info = jz_get_device_info(JZ_DEVICE_FIIOM3K); + if(!info) + return JZ_ERR_OTHER; + + int rc = fiiom3k_action_loadbuf(jz, pl, state, SPLBUF); + if(rc < 0) + return rc; + + jz_log(jz, JZ_LOG_DETAIL, "Open USB device %04x:%04x", + (unsigned int)info->vendor_id, (unsigned int)info->product_id); + rc = jz_usb_open(jz, &state->dev, info->vendor_id, info->product_id); + if(rc < 0) + return rc; + + jz_log(jz, JZ_LOG_DETAIL, "Setup device for flash access"); + jz_buffer* splbuf = state->bufs[SPLBUF]; + return jz_x1000_setup(state->dev, splbuf->size, splbuf->data); +} + +int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl) +{ + struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; + int rc; + + rc = fiiom3k_action_loadbuf(jz, pl, &state, BOOTBUF); + if(rc < 0) + goto error; + + rc = fiiom3k_action_setup(jz, pl, &state); + if(rc < 0) + goto error; + + jz_log(jz, JZ_LOG_DETAIL, "Reading boot image from device"); + rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]); + if(rc < 0) + goto error; + + jz_buffer* img_buf = state.bufs[IMGBUF]; + const char* backupfile = jz_paramlist_get(pl, "backup"); + const char* without_backup = jz_paramlist_get(pl, "without-backup"); + if(backupfile) { + jz_log(jz, JZ_LOG_DETAIL, "Backup original boot image to file: %s", backupfile); + rc = jz_buffer_save(img_buf, backupfile); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Error saving backup image file (%d): %s", rc, backupfile); + goto error; + } + } else if(!without_backup || strcmp(without_backup, "yes")) { + jz_log(jz, JZ_LOG_ERROR, "No --backup option given and --without-backup yes not specified"); + jz_log(jz, JZ_LOG_ERROR, "Refusing to flash a new bootloader without taking a backup"); + goto error; + } + + jz_log(jz, JZ_LOG_DETAIL, "Patching image with new SPL/bootloader"); + jz_buffer* boot_buf = state.bufs[BOOTBUF]; + jz_buffer* spl_buf = state.bufs[SPLBUF]; + rc = jz_fiiom3k_patchboot(jz, img_buf->data, img_buf->size, + spl_buf->data, spl_buf->size, + boot_buf->data, boot_buf->size); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Error patching image: %d", rc); + goto error; + } + + jz_log(jz, JZ_LOG_DETAIL, "Writing patched image to device"); + rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data); + if(rc < 0) + goto error; + + rc = JZ_SUCCESS; + + error: + fiiom3k_action_cleanup(&state); + return rc; +} + +int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl) +{ + struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; + int rc; + + const char* outfile_path = jz_paramlist_get(pl, IMGBUF_NAME); + if(!outfile_path) { + jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", IMGBUF_NAME); + rc = JZ_ERR_OTHER; + goto error; + } + + rc = fiiom3k_action_setup(jz, pl, &state); + if(rc < 0) + goto error; + + rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]); + if(rc < 0) + goto error; + + rc = jz_buffer_save(state.bufs[IMGBUF], outfile_path); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "Error writing '%s' file (%d): %s", IMGBUF_NAME, rc, outfile_path); + goto error; + } + + rc = JZ_SUCCESS; + + error: + fiiom3k_action_cleanup(&state); + return rc; +} + +int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl) +{ + struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; + int rc; + + rc = fiiom3k_action_loadbuf(jz, pl, &state, IMGBUF); + if(rc < 0) + goto error; + + rc = fiiom3k_action_setup(jz, pl, &state); + if(rc < 0) + goto error; + + jz_buffer* img_buf = state.bufs[IMGBUF]; + rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data); + if(rc < 0) + goto error; + + rc = JZ_SUCCESS; + + error: + fiiom3k_action_cleanup(&state); + return rc; +} diff --git a/rbutil/jztool/src/identify_file.c b/rbutil/jztool/src/identify_file.c new file mode 100644 index 0000000000..3bf4a9ced7 --- /dev/null +++ b/rbutil/jztool/src/identify_file.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 + +/* Following is copied from mkspl-x1000, basically */ +struct x1000_spl_header { + uint8_t magic[8]; + uint8_t type; + uint8_t crc7; + uint8_t ppb; + uint8_t bpp; + uint32_t length; +}; + +static const uint8_t x1000_spl_header_magic[8] = + {0x06, 0x05, 0x04, 0x03, 0x02, 0x55, 0xaa, 0x55}; + +static const size_t X1000_SPL_HEADER_SIZE = 2 * 1024; + +static uint8_t crc7(const uint8_t* buf, size_t len) +{ + /* table-based computation of CRC7 */ + static const uint8_t t[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 + }; + + uint8_t crc = 0; + while(len--) + crc = t[(crc << 1) ^ *buf++]; + return crc; +} + +int jz_identify_x1000_spl(const void* data, size_t len) +{ + /* Use <= check because a header-only file is not really valid, + * it should have at least one byte in it... */ + if(len <= X1000_SPL_HEADER_SIZE) + return JZ_IDERR_WRONG_SIZE; + + /* Look for header magic bytes */ + const struct x1000_spl_header* header = (const struct x1000_spl_header*)data; + if(memcmp(header->magic, x1000_spl_header_magic, 8)) + return JZ_IDERR_BAD_HEADER; + + /* Length stored in the header should equal the length of the file */ + if(header->length != len) + return JZ_IDERR_WRONG_SIZE; + + /* Compute the CRC7 checksum; it only covers the SPL code */ + const uint8_t* dat = (const uint8_t*)data; + uint8_t sum = crc7(&dat[X1000_SPL_HEADER_SIZE], len - X1000_SPL_HEADER_SIZE); + if(header->crc7 != sum) + return JZ_IDERR_BAD_CHECKSUM; + + return JZ_SUCCESS; + +} + +static const struct scramble_model_info { + const char* name; + int model_num; +} scramble_models[] = { + {"fiio", 114}, + {NULL, 0}, +}; + +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; + + /* 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)) + 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 += 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) + return JZ_IDERR_BAD_CHECKSUM; + + return JZ_SUCCESS; +} + +int jz_identify_fiiom3k_bootimage(const void* data, size_t len) +{ + /* The bootloader image is simply a dump of the first NAND eraseblock, + * so it has a fixed 128 KiB size */ + if(len != 128*1024) + return JZ_IDERR_WRONG_SIZE; + + /* We'll verify the embedded SPL, but we have to drag out the correct + * length from the header. Length should be more than 12 KiB, due to + * limitations of the hardware */ + const struct x1000_spl_header* spl_header; + spl_header = (const struct x1000_spl_header*)data; + if(spl_header->length > 12 * 1024) + return JZ_IDERR_BAD_HEADER; + + int rc = jz_identify_x1000_spl(data, spl_header->length); + if(rc < 0) + return rc; + + const uint8_t* dat = (const uint8_t*)data; + + /* Check the partition table is present */ + if(memcmp(&dat[0x3c00], "nand", 4)) + return JZ_IDERR_OTHER; + + /* Check first bytes of PDMA firmware. It doesn't change + * between OF versions, and Rockbox doesn't modify it. */ + static const uint8_t pdma_fw[] = {0x54, 0x25, 0x42, 0xb3, 0x70, 0x25, 0x42, 0xb3}; + if(memcmp(&dat[0x4000], pdma_fw, sizeof(pdma_fw))) + return JZ_IDERR_OTHER; + + return JZ_SUCCESS; +} diff --git a/rbutil/jztool/src/jztool_private.h b/rbutil/jztool/src/jztool_private.h new file mode 100644 index 0000000000..11299f21f9 --- /dev/null +++ b/rbutil/jztool/src/jztool_private.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * __________ __ ___. + * 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. + * + ****************************************************************************/ + +#ifndef JZTOOL_PRIVATE_H +#define JZTOOL_PRIVATE_H + +#include "jztool.h" +#include + +struct jz_context { + void* user_data; + jz_log_cb log_cb; + jz_log_level log_level; + libusb_context* usb_ctx; + int usb_ctxref; +}; + +struct jz_usbdev { + jz_context* jz; + libusb_device_handle* handle; +}; + +int jz_context_ref_libusb(jz_context* jz); +void jz_context_unref_libusb(jz_context* jz); + +#endif /* JZTOOL_PRIVATE_H */ diff --git a/rbutil/jztool/src/paramlist.c b/rbutil/jztool/src/paramlist.c new file mode 100644 index 0000000000..05bcf97a13 --- /dev/null +++ b/rbutil/jztool/src/paramlist.c @@ -0,0 +1,135 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include + +struct jz_paramlist { + int size; + char** keys; + char** values; +}; + +static int jz_paramlist_extend(jz_paramlist* pl, int count) +{ + int nsize = pl->size + count; + + /* Reallocate key list */ + char** nkeys = realloc(pl->keys, nsize * sizeof(char*)); + if(!nkeys) + return JZ_ERR_OUT_OF_MEMORY; + + for(int i = pl->size; i < nsize; ++i) + nkeys[i] = NULL; + + pl->keys = nkeys; + + /* Reallocate value list */ + char** nvalues = realloc(pl->values, nsize * sizeof(char*)); + if(!nvalues) + return JZ_ERR_OUT_OF_MEMORY; + + for(int i = pl->size; i < nsize; ++i) + nvalues[i] = NULL; + + pl->values = nvalues; + + pl->size = nsize; + return JZ_SUCCESS; +} + +jz_paramlist* jz_paramlist_new(void) +{ + jz_paramlist* pl = malloc(sizeof(struct jz_paramlist)); + if(!pl) + return NULL; + + pl->size = 0; + pl->keys = NULL; + pl->values = NULL; + return pl; +} + +void jz_paramlist_free(jz_paramlist* pl) +{ + for(int i = 0; i < pl->size; ++i) { + free(pl->keys[i]); + free(pl->values[i]); + } + + if(pl->size > 0) { + free(pl->keys); + free(pl->values); + } + + free(pl); +} + +int jz_paramlist_set(jz_paramlist* pl, const char* param, const char* value) +{ + int pos = -1; + for(int i = 0; i < pl->size; ++i) { + if(!pl->keys[i] || !strcmp(pl->keys[i], param)) { + pos = i; + break; + } + } + + if(pos == -1) { + pos = pl->size; + int rc = jz_paramlist_extend(pl, 1); + if(rc < 0) + return rc; + } + + bool need_key = (pl->keys[pos] == NULL); + if(need_key) { + char* newparam = strdup(param); + if(!newparam) + return JZ_ERR_OUT_OF_MEMORY; + + pl->keys[pos] = newparam; + } + + char* newvalue = strdup(value); + if(!newvalue) { + if(need_key) { + free(pl->keys[pos]); + pl->keys[pos] = NULL; + } + + return JZ_ERR_OUT_OF_MEMORY; + } + + pl->values[pos] = newvalue; + return JZ_SUCCESS; +} + +const char* jz_paramlist_get(jz_paramlist* pl, const char* param) +{ + for(int i = 0; i < pl->size; ++i) + if(pl->keys[i] && !strcmp(pl->keys[i], param)) + return pl->values[i]; + + return NULL; +} diff --git a/rbutil/jztool/src/usb.c b/rbutil/jztool/src/usb.c new file mode 100644 index 0000000000..7e4a5f3388 --- /dev/null +++ b/rbutil/jztool/src/usb.c @@ -0,0 +1,203 @@ +/*************************************************************************** + * __________ __ ___. + * 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_private.h" +#include +#include + +#define VR_GET_CPU_INFO 0 +#define VR_SET_DATA_ADDRESS 1 +#define VR_SET_DATA_LENGTH 2 +#define VR_FLUSH_CACHES 3 +#define VR_PROGRAM_START1 4 +#define VR_PROGRAM_START2 5 + +int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id) +{ + int rc; + jz_usbdev* dev = NULL; + libusb_device_handle* usb_handle = NULL; + libusb_device** dev_list = NULL; + ssize_t dev_index = -1, dev_count; + + rc = jz_context_ref_libusb(jz); + if(rc < 0) + return rc; + + dev = malloc(sizeof(struct jz_usbdev)); + if(!dev) { + rc = JZ_ERR_OUT_OF_MEMORY; + goto error; + } + + dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list); + if(dev_count < 0) { + jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count)); + rc = JZ_ERR_USB; + goto error; + } + + for(ssize_t i = 0; i < dev_count; ++i) { + struct libusb_device_descriptor desc; + rc = libusb_get_device_descriptor(dev_list[i], &desc); + if(rc < 0) { + jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s", + libusb_strerror(rc)); + continue; + } + + if(desc.idVendor != vend_id || desc.idProduct != prod_id) + continue; + + if(dev_index >= 0) { + /* not the best, but it is the safest thing */ + jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x", + (unsigned int)vend_id, (unsigned int)prod_id); + jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again"); + rc = JZ_ERR_NO_DEVICE; + goto error; + } + + dev_index = i; + } + + if(dev_index < 0) { + jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%05x found", + (unsigned int)vend_id, (unsigned int)prod_id); + rc = JZ_ERR_NO_DEVICE; + goto error; + } + + rc = libusb_open(dev_list[dev_index], &usb_handle); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc)); + rc = JZ_ERR_USB; + goto error; + } + + rc = libusb_claim_interface(usb_handle, 0); + if(rc < 0) { + jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc)); + rc = JZ_ERR_USB; + goto error; + } + + dev->jz = jz; + dev->handle = usb_handle; + *devptr = dev; + rc = JZ_SUCCESS; + + exit: + if(dev_list) + libusb_free_device_list(dev_list, true); + return rc; + + error: + if(dev) + free(dev); + if(usb_handle) + libusb_close(usb_handle); + jz_context_unref_libusb(jz); + goto exit; +} + +void jz_usb_close(jz_usbdev* dev) +{ + libusb_release_interface(dev->handle, 0); + libusb_close(dev->handle); + jz_context_unref_libusb(dev->jz); + free(dev); +} + +static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg) +{ + 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); + + if(rc < 0) { + jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc)); + rc = JZ_ERR_USB; + } else { + rc = JZ_SUCCESS; + } + + return rc; +} + +static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf) +{ + int xfered = 0; + int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1; + int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000); + + if(rc < 0) { + jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc)); + rc = JZ_ERR_USB; + } else if(xfered != (int)len) { + jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered"); + rc = JZ_ERR_USB; + } else { + rc = JZ_SUCCESS; + } + + return rc; +} + +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); + if(rc < 0) + return rc; + + rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len); + if(rc < 0) + return rc; + + return jz_usb_transfer(dev, write, len, data); +} + +int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data) +{ + return jz_usb_sendrecv(dev, true, addr, len, (void*)data); +} + +int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data) +{ + return jz_usb_sendrecv(dev, false, addr, len, data); +} + +int jz_usb_start1(jz_usbdev* dev, uint32_t addr) +{ + return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr); +} + +int jz_usb_start2(jz_usbdev* dev, uint32_t addr) +{ + return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr); +} + +int jz_usb_flush_caches(jz_usbdev* dev) +{ + return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0); +} diff --git a/rbutil/jztool/src/x1000.c b/rbutil/jztool/src/x1000.c new file mode 100644 index 0000000000..049344e5e6 --- /dev/null +++ b/rbutil/jztool/src/x1000.c @@ -0,0 +1,193 @@ +/*************************************************************************** + * __________ __ ___. + * 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_private.h" +#include "../../../firmware/target/mips/ingenic_x1000/spl-x1000-defs.h" +#include "../../../firmware/target/mips/ingenic_x1000/nand-x1000-err.h" +#include // TODO: portability +#include + +static const char* jz_x1000_nand_strerror(int rc) +{ + switch(rc) { + case NANDERR_CHIP_UNSUPPORTED: + return "Chip unsupported"; + case NANDERR_WRITE_PROTECTED: + return "Operation forbidden by write-protect"; + case NANDERR_UNALIGNED_ADDRESS: + return "Improperly aligned address"; + case NANDERR_UNALIGNED_LENGTH: + return "Improperly aligned length"; + case NANDERR_READ_FAILED: + return "Read operation failed"; + case NANDERR_ECC_FAILED: + return "Uncorrectable ECC error on read"; + case NANDERR_ERASE_FAILED: + return "Erase operation failed"; + case NANDERR_PROGRAM_FAILED: + return "Program operation failed"; + case NANDERR_COMMAND_FAILED: + return "NAND command failed"; + default: + return "Unknown NAND error"; + } +} + + +static int jz_x1000_send_args(jz_usbdev* dev, struct x1000_spl_arguments* args) +{ + args->command = htole32(args->command); + args->param1 = htole32(args->param1); + args->param2 = htole32(args->param2); + args->flags = htole32(args->flags); + return jz_usb_send(dev, SPL_ARGUMENTS_ADDRESS, sizeof(*args), args); +} + +static int jz_x1000_recv_status(jz_usbdev* dev, struct x1000_spl_status* status) +{ + int rc = jz_usb_recv(dev, SPL_STATUS_ADDRESS, sizeof(*status), status); + if(rc < 0) + return rc; + + status->err_code = le32toh(status->err_code); + status->reserved = le32toh(status->reserved); + return JZ_SUCCESS; +} + +int jz_x1000_setup(jz_usbdev* dev, size_t spl_len, const void* spl_data) +{ + int rc = jz_identify_x1000_spl(spl_data, spl_len); + if(rc < 0) + return JZ_ERR_BAD_FILE_FORMAT; + if(spl_len > SPL_MAX_SIZE) + return JZ_ERR_BAD_FILE_FORMAT; + + rc = jz_usb_send(dev, SPL_LOAD_ADDRESS, spl_len, spl_data); + if(rc < 0) + return rc; + + struct x1000_spl_arguments args; + args.command = SPL_CMD_BOOT; + args.param1 = SPL_BOOTOPT_NONE; + args.param2 = 0; + args.flags = 0; + rc = jz_x1000_send_args(dev, &args); + if(rc < 0) + return rc; + + rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS); + if(rc < 0) + return rc; + + jz_sleepms(100); + + struct x1000_spl_status status; + rc = jz_x1000_recv_status(dev, &status); + if(rc < 0) + return rc; + + if(status.err_code != 0) { + jz_log(dev->jz, JZ_LOG_ERROR, "X1000 device init error: %d", status.err_code); + return JZ_ERR_OTHER; + } + + return JZ_SUCCESS; +} + +int jz_x1000_read_flash(jz_usbdev* dev, uint32_t addr, size_t len, void* data) +{ + struct x1000_spl_arguments args; + args.command = SPL_CMD_FLASH_READ; + args.param1 = addr; + args.param2 = len; + args.flags = SPL_FLAG_SKIP_INIT; + int rc = jz_x1000_send_args(dev, &args); + if(rc < 0) + return rc; + + rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS); + if(rc < 0) + return rc; + + jz_sleepms(500); + + struct x1000_spl_status status; + rc = jz_x1000_recv_status(dev, &status); + if(rc < 0) + return rc; + + if(status.err_code != 0) { + jz_log(dev->jz, JZ_LOG_ERROR, "X1000 flash read error: %s", + jz_x1000_nand_strerror(status.err_code)); + return JZ_ERR_FLASH_ERROR; + } + + return jz_usb_recv(dev, SPL_BUFFER_ADDRESS, len, data); +} + +int jz_x1000_write_flash(jz_usbdev* dev, uint32_t addr, size_t len, const void* data) +{ + int rc = jz_usb_send(dev, SPL_BUFFER_ADDRESS, len, data); + if(rc < 0) + return rc; + + struct x1000_spl_arguments args; + args.command = SPL_CMD_FLASH_WRITE; + args.param1 = addr; + args.param2 = len; + args.flags = SPL_FLAG_SKIP_INIT; + rc = jz_x1000_send_args(dev, &args); + if(rc < 0) + return rc; + + rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS); + if(rc < 0) + return rc; + + jz_sleepms(500); + + struct x1000_spl_status status; + rc = jz_x1000_recv_status(dev, &status); + if(rc < 0) + return rc; + + if(status.err_code != 0) { + jz_log(dev->jz, JZ_LOG_ERROR, "X1000 flash write error: %s", + jz_x1000_nand_strerror(status.err_code)); + return JZ_ERR_FLASH_ERROR; + } + + return JZ_SUCCESS; +} + +int jz_x1000_boot_rockbox(jz_usbdev* dev) +{ + struct x1000_spl_arguments args; + args.command = SPL_CMD_BOOT; + args.param1 = SPL_BOOTOPT_ROCKBOX; + args.param2 = 0; + args.flags = 0; + int rc = jz_x1000_send_args(dev, &args); + if(rc < 0) + return rc; + + return jz_usb_start1(dev, SPL_EXEC_ADDRESS); +} -- cgit v1.2.3