summaryrefslogtreecommitdiffstats
path: root/rbutil
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-04-13 16:58:15 +0100
committerAidan MacDonald <amachronic@protonmail.com>2021-04-17 20:23:19 +0000
commitb41d53792c4c4e4abd6d810b2765d865775f5104 (patch)
treeae3e02b961c655cd49b3c13bc895a18547ceeec1 /rbutil
parent1b8542490da3283dfa0ce0f3363f16eab0609815 (diff)
downloadrockbox-b41d53792c4c4e4abd6d810b2765d865775f5104.tar.gz
rockbox-b41d53792c4c4e4abd6d810b2765d865775f5104.zip
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
Diffstat (limited to 'rbutil')
-rw-r--r--rbutil/jztool/Makefile37
-rw-r--r--rbutil/jztool/README.md60
-rw-r--r--rbutil/jztool/include/jztool.h200
-rw-r--r--rbutil/jztool/jztool.c227
-rw-r--r--rbutil/jztool/src/buffer.c134
-rw-r--r--rbutil/jztool/src/context.c167
-rw-r--r--rbutil/jztool/src/device_info.c98
-rw-r--r--rbutil/jztool/src/fiiom3k.c283
-rw-r--r--rbutil/jztool/src/identify_file.c179
-rw-r--r--rbutil/jztool/src/jztool_private.h44
-rw-r--r--rbutil/jztool/src/paramlist.c135
-rw-r--r--rbutil/jztool/src/usb.c203
-rw-r--r--rbutil/jztool/src/x1000.c193
13 files changed, 1960 insertions, 0 deletions
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 <stdint.h>
+#include <stddef.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+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] <device> <action> [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 <spl.m3k> --bootloader <bootloader.m3k>\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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+/** \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 <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <time.h>
+
+/** \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 <string.h>
+
+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 <string.h>
+
+#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 <string.h>
+
+/* 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 <libusb.h>
+
+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 <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+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 <stdlib.h>
+#include <stdbool.h>
+
+#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 <endian.h> // TODO: portability
+#include <string.h>
+
+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);
+}