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
This commit is contained in:
Aidan MacDonald 2021-04-13 16:58:15 +01:00
parent 1b8542490d
commit b41d53792c
14 changed files with 1961 additions and 0 deletions

1
.gitignore vendored
View file

@ -97,6 +97,7 @@ __pycache__
/rbutil/mknwzboot/mknwzboot
/rbutil/mkzenboot/mkzenboot
/rbutil/sansapatcher/sansapatcher
/rbutil/jztool/jztool
/rbutil/tools/bin2c
# /tools/

37
rbutil/jztool/Makefile Normal file
View file

@ -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

60
rbutil/jztool/README.md Normal file
View file

@ -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.

View file

@ -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 */

227
rbutil/jztool/jztool.c Normal file
View file

@ -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;
}

134
rbutil/jztool/src/buffer.c Normal file
View file

@ -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;
}

167
rbutil/jztool/src/context.c Normal file
View file

@ -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;
}
}

View file

@ -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;
}

283
rbutil/jztool/src/fiiom3k.c Normal file
View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 */

View file

@ -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;
}

203
rbutil/jztool/src/usb.c Normal file
View file

@ -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);
}

193
rbutil/jztool/src/x1000.c Normal file
View file

@ -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);
}