mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-09 13:12:37 -05:00
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:
parent
6c6f0757d7
commit
c876d3bbef
494 changed files with 13 additions and 13 deletions
47
utils/jztool/Makefile
Normal file
47
utils/jztool/Makefile
Normal 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
135
utils/jztool/README.md
Normal 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.)
|
||||
202
utils/jztool/include/jztool.h
Normal file
202
utils/jztool/include/jztool.h
Normal 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
212
utils/jztool/jztool.c
Normal 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
134
utils/jztool/src/buffer.c
Normal 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
177
utils/jztool/src/context.c
Normal 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;
|
||||
}
|
||||
}
|
||||
109
utils/jztool/src/device_info.c
Normal file
109
utils/jztool/src/device_info.c
Normal 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;
|
||||
}
|
||||
170
utils/jztool/src/identify_file.c
Normal file
170
utils/jztool/src/identify_file.c
Normal 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;
|
||||
}
|
||||
44
utils/jztool/src/jztool_private.h
Normal file
44
utils/jztool/src/jztool_private.h
Normal 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 */
|
||||
128
utils/jztool/src/ucl_unpack.c
Normal file
128
utils/jztool/src/ucl_unpack.c
Normal 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
291
utils/jztool/src/usb.c
Normal 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
180
utils/jztool/src/x1000.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue