rbutil: Merge rbutil with utils folder.

rbutil uses several components from the utils folder, and can be
considered part of utils too. Having it in a separate folder is an
arbitrary split that doesn't help anymore these days, so merge them.

This also allows other utils to easily use libtools.make without the
need to navigate to a different folder.

Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21
This commit is contained in:
Dominik Riebeling 2021-12-15 21:04:28 +01:00
parent 6c6f0757d7
commit c876d3bbef
494 changed files with 13 additions and 13 deletions

47
utils/jztool/Makefile Normal file
View file

@ -0,0 +1,47 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
CFLAGS += -Wall -Wextra -Iinclude -I../../tools/ucl/include -I../../lib/microtar/src
OUTPUT = jztool
ifdef RELEASE
CFLAGS += -Os -DNDEBUG
else
CFLAGS += -O0 -ggdb
endif
LIBSOURCES := src/buffer.c src/context.c src/device_info.c \
src/identify_file.c src/ucl_unpack.c src/usb.c src/x1000.c
SOURCES := $(LIBSOURCES) jztool.c
EXTRADEPS := libucl.a libmicrotar.a
CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -)
ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32)
# TODO: support Windows
else
ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE)
# Mac, tested on x86 only -- may need to adjust paths if building on ARM.
# paths should work with homebrew libusb.
LIBUSB_CFLAGS ?= -I/usr/local/include/libusb-1.0
ifdef STATIC
LIBUSB_LDOPTS ?= /usr/local/lib/libusb-1.0.a -framework IOKit -framework CoreFoundation
else
LIBUSB_LDOPTS ?= -L/usr/local/lib -lusb-1.0
endif
else
# Linux; note for static builds you need to build a copy of libusb without
# udev support and specify the includes / libs manually
LIBUSB_CFLAGS ?= `pkg-config --cflags libusb-1.0`
LIBUSB_LDOPTS ?= `pkg-config --libs libusb-1.0`
endif
endif
CFLAGS += $(LIBUSB_CFLAGS)
LDOPTS += $(LIBUSB_LDOPTS)
include ../libtools.make

135
utils/jztool/README.md Normal file
View file

@ -0,0 +1,135 @@
# jztool -- Ingenic device utility & bootloader installer
The `jztool` utility can help install, backup, and restore the bootloader on
Rockbox players based on a supported Ingenic SoC (currently only the X1000).
## Running jztool
### Getting a bootloader
To use `jztool` you need to compile or download a bootloader for your player.
It's recommended to use only official released bootloaders, since bootloaders
compiled from Git are not tested and might be buggy.
You can download released bootloaders from <https://download.rockbox.org/>.
The bootloader file is named after the target: for example, the FiiO M3K
bootloader is called `bootloader.m3k`. The FiiO M3K is used as an example
here, but the instructions apply to all X1000-based players.
Use `jztool --help` to find out the model name of your player.
### Entering USB boot mode
USB boot mode is a low-level mode provided by the CPU which allows a computer
to load firmware onto the device. You need to put your player into this mode
manually before using `jztool` (unfortunately, it can't be done automatically.)
To connect the player in USB boot mode, follow these steps:
1. Ensure the player is fully powered off.
2. Plug one end of the USB cable into your player.
3. Hold down your player's USB boot key (see below).
4. Plug the other end of the USB cable into your computer.
5. Let go of the USB boot key.
The USB boot key depends on your player:
- FiiO M3K: Volume Down
- Shanling Q1: Play
- Eros Q: Menu
### Linux/Mac
Run the following command in a terminal. Note that on Linux, you will need to
have root access to allow libusb to access the USB device.
```sh
# Linux / Mac
# NOTE: root permissions are required on Linux to access the USB device
# eg. with 'sudo' or 'su -c' depending on your distro.
$ ./jztool fiiom3k load bootloader.m3k
```
### Windows
To allow `jztool` access to your player in USB boot mode, you need to install
the WinUSB driver. The recommended way to install it is using Zadig, which
may be downloaded from its homepage <https://zadig.akeo.ie>. Please note
this is 3rd party software not maintained or supported by Rockbox developers.
(Zadig will require administrator access on the machine you are using.)
When running Zadig you must select the WinUSB driver; the other driver options
will not work properly with `jztool`. You will have to select the correct USB
device in Zadig. All X1000-based players use the same USB ID while in USB boot
mode, listed below. NOTE: the device name may show only as "X" and a hollow
square in Zadig. The IDs will not change, so those are the most reliable way
to confirm you have selected the correct device.
```
Name: Ingenic Semiconductor Co.,Ltd X1000
USB ID: A108 1000
```
Assuming you installed the WinUSB driver successfully, open a command prompt
in the folder containing `jztool`. Administrator access is not required for
this step.
Type the following command to load the Rockbox bootloader:
```sh
# Windows
$ jztool.exe fiiom3k load bootloader.m3k
```
## Using the recovery menu
If `jztool` runs successfully your player will display the Rockbox bootloader's
recovery menu. If you want to permanently install Rockbox to your device, copy
the bootloader file you downloaded to the root of your SD card, insert the SD
card to your player, and choose "Install/update bootloader" from the menu.
It is _highly_ recommended that you take a backup of your existing bootloader
in case of any trouble -- choose "Backup bootloader" from the recovery menu.
The backup file is called `PLAYER-boot.bin`, where `PLAYER` is the model name.
(Example: `fiiom3k-boot.bin`.)
You can restore the backup later by putting it on the root of your SD card and
selecting "Restor bootloader" in the recovery menu.
After installing the Rockbox bootloader, you can access the recovery menu by
holding a key while booting:
- FiiO M3K: Volume Up
- Shanling Q1: Next (button on the lower left)
- Eros Q: Volume Up
### Known issues
- When using the bootloader's USB mode, you may get stuck on "Waiting for USB"
even though the cable is already plugged in. If this occurs, unplug the USB
cable and plug it back in to trigger the connection.
## 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.
Permissions are an issue on Linux because by default only root can access
"raw" USB devices. If we want to package rbutil for distro we can install
a udev rule to allow access to the specific USB IDs we need, eg. allowing
users in the "wheel" group to access the device.
On Windows and Mac, no special permissions are needed to access USB devices
assuming the drivers are set up. (Zadig does require administrator access
to run, but that's external to the Rockbox utility.)

View file

@ -0,0 +1,202 @@
/***************************************************************************
* __________ __ ___.
* 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
*/
#define JZ_CPUINFO_BUFLEN 9
typedef struct jz_context jz_context;
typedef struct jz_usbdev jz_usbdev;
typedef struct jz_device_info jz_device_info;
typedef struct jz_cpu_info jz_cpu_info;
typedef struct jz_buffer jz_buffer;
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*);
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,
JZ_DEVICE_SHANLINGQ1,
JZ_DEVICE_EROSQ,
JZ_NUM_DEVICES,
};
enum jz_cpu_type {
JZ_CPU_X1000,
JZ_NUM_CPUS,
};
struct jz_device_info {
/* internal device name and file extension */
const char* name;
const char* file_ext;
/* human-readable name */
const char* description;
/* device and CPU type */
jz_device_type device_type;
jz_cpu_type cpu_type;
/* USB IDs of the device in mass storage mode */
uint16_t vendor_id;
uint16_t product_id;
};
struct jz_cpu_info {
/* CPU info string, as reported by the boot ROM */
const char* info_str;
/* USB IDs of the boot ROM */
uint16_t vendor_id;
uint16_t product_id;
/* default addresses for running binaries */
uint32_t stage1_load_addr;
uint32_t stage1_exec_addr;
uint32_t stage2_load_addr;
uint32_t stage2_exec_addr;
};
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
*/
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);
const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type);
const jz_cpu_info* jz_get_cpu_info_named(const char* info_str);
int jz_identify_x1000_spl(const void* data, size_t len);
int jz_identify_scramble_image(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);
int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen);
/******************************************************************************
* Rockbox loader (all functions are model-specific, see docs)
*/
int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename);
/******************************************************************************
* Buffer API and other functions
*/
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);
jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len);
/******************************************************************************
* END
*/
#ifdef __cplusplus
}
#endif
#endif /* JZTOOL_H */

212
utils/jztool/jztool.c Normal file
View file

@ -0,0 +1,212 @@
/***************************************************************************
* __________ __ ___.
* 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;
jz_usbdev* usbdev = NULL;
const jz_device_info* dev_info = NULL;
const jz_cpu_info* cpu_info = NULL;
void usage_x1000(void)
{
printf(
"Usage:\n"
" jztool fiiom3k load <bootloader.m3k>\n"
" jztool shanlingq1 load <bootloader.q1>\n"
" jztool erosq load <bootloader.erosq>\n"
"\n"
"The 'load' command is used to boot the Rockbox bootloader in recovery\n"
"mode, which allows you to install the Rockbox bootloader and backup or\n"
"restore bootloader images. You need to connect your player in USB boot\n"
"mode in order to use this tool.\n"
"\n"
"To connect the player in USB boot mode, follow these steps:\n"
"\n"
"1. Ensure the player is fully powered off.\n"
"2. Plug one end of the USB cable into your player.\n"
"3. Hold down your player's USB boot key (see below).\n"
"4. Plug the other end of the USB cable into your computer.\n"
"5. Let go of the USB boot key.\n"
"\n"
"USB boot keys:\n"
"\n"
" FiiO M3K - Volume Down\n"
" Shanling Q1 - Play\n"
" Eros Q - Menu\n"
"\n"
"Not all players give a visible indication that they are in USB boot mode.\n"
"If you're having trouble connecting your player, try resetting it by\n"
"holding the power button for 10 seconds, and try the above steps again.\n"
"\n"
"Note for Windows users: you need to install the WinUSB driver using a\n"
"3rd-party tool such as Zadig <https://zadig.akeo.ie> before this tool\n"
"can access your player in USB boot mode. You need to run Zadig while the\n"
"player is plugged in and in USB boot mode. For more details check the\n"
"jztool README.md file or visit <https://rockbox.org/wiki/IngenicX1000>.\n"
"\n");
exit(4);
}
int cmdline_x1000(int argc, char** argv)
{
if(argc < 2 || strcmp(argv[0], "load")) {
usage_x1000();
return 2;
}
int rc = jz_usb_open(jz, &usbdev, cpu_info->vendor_id, cpu_info->product_id);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Cannot open USB device: %d", rc);
return 1;
}
rc = jz_x1000_boot(usbdev, dev_info->device_type, argv[1]);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Boot failed: %d", rc);
return 1;
}
return 0;
}
void usage(void)
{
printf("Usage:\n"
" jztool [global options] <device> <command> [command arguments]\n"
"\n"
"Global options:\n"
" -h, --help Display this help\n"
" -q, --quiet Don't log anything except errors\n"
" -v, --verbose Display detailed logging output\n\n");
printf("Supported devices:\n\n");
for(int i = 0; i < JZ_NUM_DEVICES; ++i) {
const jz_device_info* info = jz_get_device_info_indexed(i);
printf(" %s - %s\n", info->name, info->description);
}
printf("\n"
"For device-specific help run 'jztool DEVICE' without arguments,\n"
"eg. 'jztool fiiom3k' will display help for the FiiO M3K.\n");
exit(4);
}
void cleanup(void)
{
if(usbdev)
jz_usb_close(usbdev);
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);
}
cpu_info = jz_get_cpu_info(dev_info->cpu_type);
/* Dispatch to device handler */
--argc, ++argv;
switch(dev_info->device_type) {
case JZ_DEVICE_FIIOM3K:
case JZ_DEVICE_SHANLINGQ1:
case JZ_DEVICE_EROSQ:
return cmdline_x1000(argc, argv);
default:
jz_log(jz, JZ_LOG_ERROR, "INTERNAL ERROR: unhandled device type");
return 1;
}
}

134
utils/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;
}

177
utils/jztool/src/context.c Normal file
View file

@ -0,0 +1,177 @@
/***************************************************************************
* __________ __ ___.
* 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>
#ifdef WIN32
# include <windows.h>
#endif
/** \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)
{
#ifdef WIN32
Sleep(ms);
#else
struct timespec ts;
long ns = ms % 1000;
ts.tv_nsec = ns * 1000 * 1000;
ts.tv_sec = ms / 1000;
nanosleep(&ts, NULL);
#endif
}
/** \brief Add reference to libusb context, allocating it if necessary */
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;
}
/** \brief Remove reference to libusb context, freeing if it hits zero */
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,109 @@
/***************************************************************************
* __________ __ ___.
* 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 jz_device_info infotable[JZ_NUM_DEVICES] = {
[JZ_DEVICE_FIIOM3K] = {
.name = "fiiom3k",
.file_ext = "m3k",
.description = "FiiO M3K",
.device_type = JZ_DEVICE_FIIOM3K,
.cpu_type = JZ_CPU_X1000,
.vendor_id = 0x2972,
.product_id = 0x0003,
},
[JZ_DEVICE_SHANLINGQ1] = {
.name = "shanlingq1",
.file_ext = "q1",
.description = "Shanling Q1",
.device_type = JZ_DEVICE_SHANLINGQ1,
.cpu_type = JZ_CPU_X1000,
.vendor_id = 0x0525,
.product_id = 0xa4a5,
},
[JZ_DEVICE_EROSQ] = {
.name = "erosq",
.file_ext = "erosq",
.description = "AIGO Eros Q",
.device_type = JZ_DEVICE_EROSQ,
.cpu_type = JZ_CPU_X1000,
.vendor_id = 0xc502,
.product_id = 0x0023,
},
};
static const jz_cpu_info cputable[JZ_NUM_CPUS] = {
[JZ_CPU_X1000] = {
.info_str = "X1000_v1",
.vendor_id = 0xa108,
.product_id = 0x1000,
.stage1_load_addr = 0xf4001000,
.stage1_exec_addr = 0xf4001800,
.stage2_load_addr = 0x80004000,
.stage2_exec_addr = 0x80004000,
},
};
/** \brief Lookup info for a device by type, returns NULL if not found. */
const jz_device_info* jz_get_device_info(jz_device_type type)
{
return jz_get_device_info_indexed(type);
}
/** \brief Lookup info for a device by name, returns NULL if not found. */
const jz_device_info* jz_get_device_info_named(const char* name)
{
for(int i = 0; i < JZ_NUM_DEVICES; ++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 < JZ_NUM_DEVICES)
return &infotable[index];
else
return NULL;
}
/** \brief Lookup info for a CPU, returns NULL if not found. */
const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type)
{
if(type < JZ_NUM_CPUS)
return &cputable[type];
else
return NULL;
}
/** \brief Lookup info for a CPU by info string, returns NULL if not found. */
const jz_cpu_info* jz_get_cpu_info_named(const char* info_str)
{
for(int i = 0; i < JZ_NUM_CPUS; ++i)
if(!strcmp(cputable[i].info_str, info_str))
return &cputable[i];
return NULL;
}

View file

@ -0,0 +1,170 @@
/***************************************************************************
* __________ __ ___.
* 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;
}
/** \brief Identify a file as an SPL for X1000 CPUs
* \param data File data buffer
* \param len Length of file
* \return JZ_SUCCESS if file looks correct, or one of the following errors
* \retval JZ_IDERR_WRONG_SIZE file too small or size doesn't match header
* \retval JZ_IDERR_BAD_HEADER missing magic bytes from header
* \retval JZ_IDERR_BAD_CHECKSUM CRC7 mismatch
*/
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;
size_t offset_crc;
size_t offset_name;
size_t offset_data;
} scramble_models[] = {
{"fiio", 114, 0, 4, 8},
{"shq1", 115, 0, 4, 8},
{"eros", 116, 0, 4, 8},
{NULL, 0, 0, 0, 0},
};
/** \brief Identify a file as a Rockbox `scramble` image
* \param data File data buffer
* \param len Length of file
* \return JZ_SUCCESS if file looks correct, or one of the following errors
* \retval JZ_IDERR_UNRECOGNIZED_MODEL unsupported/unknown model type
* \retval JZ_IDERR_BAD_CHECKSUM checksum mismatch
*/
int jz_identify_scramble_image(const void* data, size_t len)
{
const uint8_t* dat;
const struct scramble_model_info* model_info;
uint32_t sum, file_sum;
dat = (const uint8_t*)data;
model_info = &scramble_models[0];
/* Look up the model number */
for(; model_info->name != NULL; ++model_info) {
if(model_info->offset_name + 4 > len)
continue;
if(!memcmp(&dat[model_info->offset_name], model_info->name, 4))
break;
}
if(model_info->name == NULL)
return JZ_IDERR_UNRECOGNIZED_MODEL;
/* Compute the checksum */
sum = model_info->model_num;
for(size_t i = model_info->offset_data; i < len; ++i)
sum += dat[i];
/* Compare with file's checksum, it's stored in big-endian form */
dat += model_info->offset_crc;
file_sum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
if(sum != file_sum)
return JZ_IDERR_BAD_CHECKSUM;
return JZ_SUCCESS;
}

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,128 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "jztool.h"
#include "ucl/ucl.h"
static uint32_t xread32(const uint8_t* d)
{
uint32_t r = 0;
r |= d[0] << 24;
r |= d[1] << 16;
r |= d[2] << 8;
r |= d[3] << 0;
return r;
}
/* adapted from firmware/common/ucl_decompress.c */
jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len)
{
static const uint8_t magic[8] =
{0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a};
jz_buffer* buffer = NULL;
/* make sure there are enough bytes for the header */
if(src_len < 18)
goto error;
/* avoid memcmp for reasons of code size */
for(size_t i = 0; i < sizeof(magic); ++i)
if(src[i] != magic[i])
goto error;
/* read the other header fields */
/* uint32_t flags = xread32(&src[8]); */
uint8_t method = src[12];
/* uint8_t level = src[13]; */
uint32_t block_size = xread32(&src[14]);
/* check supported compression method */
if(method != 0x2e)
goto error;
/* validate */
if(block_size < 1024 || block_size > 8*1024*1024)
goto error;
src += 18;
src_len -= 18;
/* Calculate amount of space that we might need & allocate a buffer:
* - subtract 4 to account for end of file marker
* - each block is block_size bytes + 8 bytes of header
* - add one to nr_blocks to account for case where file size < block size
* - total size = max uncompressed size of block * nr_blocks
*/
uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1;
uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256);
buffer = jz_buffer_alloc(max_size, NULL);
if(!buffer)
goto error;
/* perform the decompression */
uint32_t dst_ilen = buffer->size;
uint8_t* dst = buffer->data;
while(1) {
if(src_len < 4)
goto error;
uint32_t out_len = xread32(src); src += 4, src_len -= 4;
if(out_len == 0)
break;
if(src_len < 4)
goto error;
uint32_t in_len = xread32(src); src += 4, src_len -= 4;
if(in_len > block_size || out_len > block_size ||
in_len == 0 || in_len > out_len)
goto error;
if(src_len < in_len)
goto error;
if(in_len < out_len) {
uint32_t actual_out_len = dst_ilen;
int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL);
if(rc != UCL_E_OK)
goto error;
if(actual_out_len != out_len)
goto error;
} else {
for(size_t i = 0; i < in_len; ++i)
dst[i] = src[i];
}
src += in_len;
src_len -= in_len;
dst += out_len;
dst_ilen -= out_len;
}
/* subtract leftover number of bytes to get size of compressed output */
*dst_len = buffer->size - dst_ilen;
return buffer;
error:
jz_buffer_free(buffer);
return NULL;
}

291
utils/jztool/src/usb.c Normal file
View file

@ -0,0 +1,291 @@
/***************************************************************************
* __________ __ ___.
* 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>
#include <string.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
/** \brief Open a USB device
* \param jz Context
* \param devptr Returns pointer to the USB device upon success
* \param vend_id USB vendor ID
* \param prod_id USB product ID
* \return either JZ_SUCCESS if device was opened, or an error below
* \retval JZ_ERR_OUT_OF_MEMORY malloc failed
* \retval JZ_ERR_USB libusb error (details are logged)
* \retval JZ_ERR_NO_DEVICE can't unambiguously find the device
*/
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:%04x 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;
}
jz_log(jz, JZ_LOG_DEBUG, "Opened device (%p, ID %04x:%04x)",
dev, (unsigned int)vend_id, (unsigned int)prod_id);
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;
}
/** \brief Close a USB device
* \param dev Device to close; memory will be freed automatically
*/
void jz_usb_close(jz_usbdev* dev)
{
jz_log(dev->jz, JZ_LOG_DEBUG, "Closing device (%p)", dev);
libusb_release_interface(dev->handle, 0);
libusb_close(dev->handle);
jz_context_unref_libusb(dev->jz);
free(dev);
}
// Does an Ingenic-specific vendor request
// Written with X1000 in mind but other Ingenic CPUs have the same commands
static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg,
void* buffer, int buflen)
{
int rc = libusb_control_transfer(dev->handle,
LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
req, arg >> 16, arg & 0xffff, buffer, buflen, 1000);
if(rc < 0) {
jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc));
rc = JZ_ERR_USB;
} else {
static const char* req_names[] = {
"GET_CPU_INFO",
"SET_DATA_ADDRESS",
"SET_DATA_LENGTH",
"FLUSH_CACHES",
"PROGRAM_START1",
"PROGRAM_START2",
};
jz_log(dev->jz, JZ_LOG_DEBUG, "Issued %s %08lu",
req_names[req], (unsigned long)arg);
rc = JZ_SUCCESS;
}
return rc;
}
// Bulk transfer wrapper
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 {
jz_log(dev->jz, JZ_LOG_DEBUG, "Transferred %zu bytes %s",
len, write ? "to device" : "from device");
rc = JZ_SUCCESS;
}
return rc;
}
// Memory send/receive primitive, performs the necessary vendor requests
// and then tranfers data using the bulk endpoint
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, NULL, 0);
if(rc < 0)
return rc;
rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0);
if(rc < 0)
return rc;
return jz_usb_transfer(dev, write, len, data);
}
/** \brief Write data to device memory
* \param dev USB device
* \param addr Address where data should be written
* \param len Length of the data, in bytes, should be positive
* \param data Data buffer
* \return either JZ_SUCCESS on success or a failure code
*/
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);
}
/** \brief Read data to device memory
* \param dev USB device
* \param addr Address to read from
* \param len Length of the data, in bytes, should be positive
* \param data Data buffer
* \return either JZ_SUCCESS on success or a failure code
*/
int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data)
{
return jz_usb_sendrecv(dev, false, addr, len, data);
}
/** \brief Execute stage1 program jumping to the specified address
* \param dev USB device
* \param addr Address to begin execution at
* \return either JZ_SUCCESS on success or a failure code
*/
int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
{
return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0);
}
/** \brief Execute stage2 program jumping to the specified address
* \param dev USB device
* \param addr Address to begin execution at
* \return either JZ_SUCCESS on success or a failure code
*/
int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
{
return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0);
}
/** \brief Ask device to flush CPU caches
* \param dev USB device
* \return either JZ_SUCCESS on success or a failure code
*/
int jz_usb_flush_caches(jz_usbdev* dev)
{
return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0);
}
/** \brief Ask device for CPU info string
* \param dev USB device
* \param buffer Buffer to hold the info string
* \param buflen Size of the buffer, in bytes
* \return either JZ_SUCCESS on success or a failure code
*
* The buffer will always be null terminated, but to ensure the info string is
* not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long.
*/
int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen)
{
char tmpbuf[JZ_CPUINFO_BUFLEN];
int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8);
if(rc != JZ_SUCCESS)
return rc;
if(buflen > 0) {
strncpy(buffer, tmpbuf, buflen);
buffer[buflen - 1] = 0;
}
return JZ_SUCCESS;
}

180
utils/jztool/src/x1000.c Normal file
View file

@ -0,0 +1,180 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "jztool.h"
#include "jztool_private.h"
#include "microtar-stdio.h"
#include <stdbool.h>
#include <string.h>
/* TODO: these functions could be refactored to be CPU-agnostic */
static int run_stage1(jz_usbdev* dev, jz_buffer* buf)
{
int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data);
if(rc < 0)
return rc;
return jz_usb_start1(dev, 0xf4001800);
}
static int run_stage2(jz_usbdev* dev, jz_buffer* buf)
{
int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data);
if(rc < 0)
return rc;
rc = jz_usb_flush_caches(dev);
if(rc < 0)
return rc;
return jz_usb_start2(dev, 0x80004000);
}
static int get_file(jz_context* jz, mtar_t* tar, const char* file,
bool decompress, jz_buffer** buf)
{
jz_buffer* buffer = NULL;
const mtar_header_t* h;
int rc;
rc = mtar_find(tar, file);
if(rc != MTAR_ESUCCESS) {
jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc);
return JZ_ERR_BAD_FILE_FORMAT;
}
h = mtar_get_header(tar);
buffer = jz_buffer_alloc(h->size, NULL);
if(!buffer)
return JZ_ERR_OUT_OF_MEMORY;
rc = mtar_read_data(tar, buffer->data, buffer->size);
if(rc < 0 || (unsigned)rc != buffer->size) {
jz_buffer_free(buffer);
jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", file, rc);
return JZ_ERR_BAD_FILE_FORMAT;
}
if(decompress) {
uint32_t dst_len;
jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len);
jz_buffer_free(buffer);
if(!nbuf) {
jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file);
return JZ_ERR_BAD_FILE_FORMAT;
}
/* for simplicity just forget original size of buffer */
nbuf->size = dst_len;
buffer = nbuf;
}
*buf = buffer;
return JZ_SUCCESS;
}
static int show_version(jz_context* jz, jz_buffer* info_file)
{
/* Extract the version string and log it for informational purposes */
char* boot_version = (char*)info_file->data;
char* endpos = memchr(boot_version, '\n', info_file->size);
if(!endpos) {
jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file");
return JZ_ERR_BAD_FILE_FORMAT;
}
*endpos = 0;
jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version);
return JZ_SUCCESS;
}
/** \brief Load the Rockbox bootloader on an X1000 device
* \param dev USB device freshly returned by jz_usb_open()
* \param filename Path to the "bootloader.target" file
* \return either JZ_SUCCESS or an error code
*/
int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename)
{
const jz_device_info* dev_info;
char spl_filename[32];
jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
mtar_t tar;
int rc;
/* In retrospect using a model-dependent archive format was not a good
* idea, but it's not worth fixing just yet... */
dev_info = jz_get_device_info(type);
if(!dev_info)
return JZ_ERR_OTHER;
/* use of sprintf is safe since file_ext is short */
sprintf(spl_filename, "spl.%s", dev_info->file_ext);
/* Now open the archive */
rc = mtar_open(&tar, filename, "rb");
if(rc != MTAR_ESUCCESS) {
jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
return JZ_ERR_OPEN_FILE;
}
/* Extract all necessary files */
rc = get_file(dev->jz, &tar, spl_filename, false, &spl);
if(rc != JZ_SUCCESS)
goto error;
rc = get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
if(rc != JZ_SUCCESS)
goto error;
rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
if(rc != JZ_SUCCESS)
goto error;
/* Display the version string */
rc = show_version(dev->jz, info_file);
if(rc != JZ_SUCCESS)
goto error;
/* Stage1 boot of SPL to set up hardware */
rc = run_stage1(dev, spl);
if(rc != JZ_SUCCESS)
goto error;
/* Need a bit of time for SPL to handle init */
jz_sleepms(500);
/* Stage2 boot into the bootloader's recovery menu
* User has to take manual action from there */
rc = run_stage2(dev, bootloader);
if(rc != JZ_SUCCESS)
goto error;
rc = JZ_SUCCESS;
error:
if(spl)
jz_buffer_free(spl);
if(bootloader)
jz_buffer_free(bootloader);
if(info_file)
jz_buffer_free(info_file);
mtar_close(&tar);
return rc;
}