jztool: add support for Shanling Q1 and Eros Q
Change-Id: I8e93162d1a9d82e9d132219f2803b1856d24ae6c
This commit is contained in:
parent
6f042e91dd
commit
740a50687f
|
@ -15,7 +15,7 @@ CFLAGS += -O0 -ggdb
|
|||
endif
|
||||
|
||||
LIBSOURCES := src/buffer.c src/context.c src/device_info.c \
|
||||
src/identify_file.c src/fiiom3k.c src/usb.c
|
||||
src/identify_file.c src/ucl_unpack.c src/usb.c src/x1000.c
|
||||
SOURCES := $(LIBSOURCES) jztool.c
|
||||
EXTRADEPS := libucl.a libmicrotar.a
|
||||
|
||||
|
|
|
@ -1,26 +1,45 @@
|
|||
# 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.
|
||||
Rockbox players based on a supported Ingenic SoC (currently only the X1000).
|
||||
|
||||
## FiiO M3K
|
||||
## Running jztool
|
||||
|
||||
First, get a copy of the `bootloader.m3k` file, either by downloading it
|
||||
from <https://rockbox.org>, or by compiling it yourself (choose 'B'ootloader
|
||||
build when configuring your build).
|
||||
### Getting a bootloader
|
||||
|
||||
The first time you install Rockbox, you need to load the Rockbox bootloader
|
||||
over USB by entering USB boot mode. The easiest way to do this is by plugging
|
||||
in the microUSB cable to the M3K and holding the VOL- button while plugging
|
||||
the USB into your computer. If you entered USB boot mode, the button light
|
||||
will turn on but the LCD will remain black.
|
||||
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.
|
||||
|
||||
Copy the `bootloader.m3k` next to the `jztool` executable and follow the
|
||||
instructions below which are appropriate to your OS.
|
||||
You can download released bootloaders from <https://download.rockbox.org/>.
|
||||
|
||||
### Running jztool
|
||||
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.
|
||||
|
||||
#### Linux/Mac
|
||||
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.
|
||||
|
@ -32,9 +51,9 @@ have root access to allow libusb to access the USB device.
|
|||
$ ./jztool fiiom3k load bootloader.m3k
|
||||
```
|
||||
|
||||
#### Windows
|
||||
### Windows
|
||||
|
||||
To allow `jztool` access to the M3K in USB boot mode, you need to install
|
||||
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.
|
||||
|
@ -42,10 +61,10 @@ this is 3rd party software not maintained or supported by Rockbox developers.
|
|||
|
||||
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 -- the name and USB IDs of the M3K in USB boot mode are 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.
|
||||
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
|
||||
|
@ -63,21 +82,27 @@ Type the following command to load the Rockbox bootloader:
|
|||
$ jztool.exe fiiom3k load bootloader.m3k
|
||||
```
|
||||
|
||||
### Further instructions
|
||||
## Using the recovery menu
|
||||
|
||||
After running `jztool` successfully your M3K will display the recovery menu
|
||||
of the Rockbox bootloader. If you want to permanently install Rockbox to your
|
||||
M3K, copy `bootloader.m3k` to the root of an SD card, insert it to your device,
|
||||
then choose "Install/update bootloader" from the 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 "fiiom3k-boot.bin" and will be saved to the root of
|
||||
the SD card. If you need to restore it, simply place the file at the root of
|
||||
your SD card and select "Restore bootloader".
|
||||
The backup file is called `PLAYER-boot.bin`, where `PLAYER` is the model name.
|
||||
(Example: `fiiom3k-boot.bin`.)
|
||||
|
||||
In the future if you want to backup, restore, or update the bootloader, you
|
||||
can access the Rockbox bootloader's recovery menu by holding VOL+ when booting.
|
||||
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
|
||||
|
||||
|
|
|
@ -33,9 +33,12 @@ extern "C" {
|
|||
* 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;
|
||||
|
@ -77,21 +80,48 @@ enum jz_log_level {
|
|||
|
||||
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;
|
||||
|
@ -119,11 +149,13 @@ void jz_sleepms(int ms);
|
|||
* Device and file info
|
||||
*/
|
||||
|
||||
int jz_get_num_device_info(void);
|
||||
const jz_device_info* jz_get_device_info(jz_device_type type);
|
||||
const jz_device_info* jz_get_device_info_named(const char* name);
|
||||
const jz_device_info* jz_get_device_info_indexed(int index);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -139,15 +171,16 @@ 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_fiiom3k_boot(jz_usbdev* dev, const char* filename);
|
||||
int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename);
|
||||
|
||||
/******************************************************************************
|
||||
* Simple buffer API
|
||||
* Buffer API and other functions
|
||||
*/
|
||||
|
||||
jz_buffer* jz_buffer_alloc(size_t size, const void* data);
|
||||
|
@ -156,6 +189,8 @@ 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
|
||||
*/
|
||||
|
|
|
@ -28,49 +28,63 @@
|
|||
jz_context* jz = NULL;
|
||||
jz_usbdev* usbdev = NULL;
|
||||
const jz_device_info* dev_info = NULL;
|
||||
const jz_cpu_info* cpu_info = NULL;
|
||||
|
||||
void usage_fiiom3k(void)
|
||||
void usage_x1000(void)
|
||||
{
|
||||
printf("Usage:\n"
|
||||
" jztool fiiom3k load <bootloader.m3k>\n"
|
||||
"\n"
|
||||
"The 'load' command is used to boot the Rockbox bootloader in\n"
|
||||
"recovery mode, which allows you to install the Rockbox bootloader\n"
|
||||
"and backup or restore bootloader images. You need to connect the\n"
|
||||
"M3K in USB boot mode in order to use this tool.\n"
|
||||
"\n"
|
||||
"On Windows, you will need to install the WinUSB driver for the M3K\n"
|
||||
"using a 3rd-party tool such as Zadig <https://zadig.akeo.ie>. For\n"
|
||||
"more details check the jztool README.md file or the Rockbox wiki at\n"
|
||||
"<https://rockbox.org/wiki/FiioM3K>.\n"
|
||||
"\n"
|
||||
"To connect the M3K in USB boot mode, plug the microUSB into the\n"
|
||||
"M3K, and hold the VOL- button while plugging the USB into your\n"
|
||||
"computer. If successful, the button light will turn on and the\n"
|
||||
"LCD will remain black. If you encounter any errors and need to\n"
|
||||
"reconnect the device, you must force a power off by holding POWER\n"
|
||||
"for more than 10 seconds.\n"
|
||||
"\n"
|
||||
"Once the Rockbox bootloader is installed on your M3K, you can\n"
|
||||
"access the recovery menu by holding VOL+ while powering on the\n"
|
||||
"device.\n");
|
||||
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_fiiom3k(int argc, char** argv)
|
||||
int cmdline_x1000(int argc, char** argv)
|
||||
{
|
||||
if(argc < 2 || strcmp(argv[0], "load")) {
|
||||
usage_fiiom3k();
|
||||
usage_x1000();
|
||||
return 2;
|
||||
}
|
||||
|
||||
int rc = jz_usb_open(jz, &usbdev, dev_info->vendor_id, dev_info->product_id);
|
||||
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_fiiom3k_boot(usbdev, argv[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;
|
||||
|
@ -90,8 +104,7 @@ void usage(void)
|
|||
" -v, --verbose Display detailed logging output\n\n");
|
||||
|
||||
printf("Supported devices:\n\n");
|
||||
int n = jz_get_num_device_info();
|
||||
for(int i = 0; i < n; ++i) {
|
||||
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);
|
||||
}
|
||||
|
@ -182,11 +195,15 @@ int main(int argc, char** 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:
|
||||
return cmdline_fiiom3k(argc, argv);
|
||||
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");
|
||||
|
|
|
@ -22,39 +22,58 @@
|
|||
#include "jztool.h"
|
||||
#include <string.h>
|
||||
|
||||
static const jz_device_info infotable[] = {
|
||||
{
|
||||
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 = 0xa108,
|
||||
.product_id = 0x1000,
|
||||
.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 int infotable_size = sizeof(infotable)/sizeof(struct jz_device_info);
|
||||
|
||||
/** \brief Get the number of entries in the device info list */
|
||||
int jz_get_num_device_info(void)
|
||||
{
|
||||
return infotable_size;
|
||||
}
|
||||
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)
|
||||
{
|
||||
for(int i = 0; i < infotable_size; ++i)
|
||||
if(infotable[i].device_type == type)
|
||||
return &infotable[i];
|
||||
|
||||
return NULL;
|
||||
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 < infotable_size; ++i)
|
||||
for(int i = 0; i < JZ_NUM_DEVICES; ++i)
|
||||
if(!strcmp(infotable[i].name, name))
|
||||
return &infotable[i];
|
||||
|
||||
|
@ -64,8 +83,27 @@ const jz_device_info* jz_get_device_info_named(const char* name)
|
|||
/** \brief Get a device info entry by index, returns NULL if out of range. */
|
||||
const jz_device_info* jz_get_device_info_indexed(int index)
|
||||
{
|
||||
if(index < infotable_size)
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -118,43 +118,52 @@ int jz_identify_x1000_spl(const void* data, size_t len)
|
|||
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},
|
||||
{NULL, 0},
|
||||
{"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_WRONG_SIZE file too small to be valid
|
||||
* \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)
|
||||
{
|
||||
/* 4 bytes checksum + 4 bytes player model */
|
||||
if(len < 8)
|
||||
return JZ_IDERR_WRONG_SIZE;
|
||||
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 */
|
||||
const uint8_t* dat = (const uint8_t*)data;
|
||||
const struct scramble_model_info* model_info = &scramble_models[0];
|
||||
for(; model_info->name != NULL; ++model_info)
|
||||
if(!memcmp(&dat[4], model_info->name, 4))
|
||||
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 */
|
||||
uint32_t sum = model_info->model_num;
|
||||
for(size_t i = 8; i < len; ++i)
|
||||
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 */
|
||||
uint32_t fsum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
|
||||
if(sum != fsum)
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
#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
|
||||
|
@ -145,11 +146,12 @@ void jz_usb_close(jz_usbdev* 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)
|
||||
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, NULL, 0, 1000);
|
||||
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));
|
||||
|
@ -200,11 +202,11 @@ 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);
|
||||
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);
|
||||
rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0);
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
|
||||
|
@ -242,7 +244,7 @@ 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)
|
||||
{
|
||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr);
|
||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0);
|
||||
}
|
||||
|
||||
/** \brief Execute stage2 program jumping to the specified address
|
||||
|
@ -252,7 +254,7 @@ int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
|
|||
*/
|
||||
int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
|
||||
{
|
||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr);
|
||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0);
|
||||
}
|
||||
|
||||
/** \brief Ask device to flush CPU caches
|
||||
|
@ -261,5 +263,29 @@ int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
|
|||
*/
|
||||
int jz_usb_flush_caches(jz_usbdev* dev)
|
||||
{
|
||||
return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -22,117 +22,11 @@
|
|||
#include "jztool.h"
|
||||
#include "jztool_private.h"
|
||||
#include "microtar.h"
|
||||
#include "ucl/ucl.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.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 */
|
||||
static jz_buffer* 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;
|
||||
}
|
||||
|
||||
static int m3k_stage1(jz_usbdev* dev, jz_buffer* buf)
|
||||
/* 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)
|
||||
|
@ -141,7 +35,7 @@ static int m3k_stage1(jz_usbdev* dev, jz_buffer* buf)
|
|||
return jz_usb_start1(dev, 0xf4001800);
|
||||
}
|
||||
|
||||
static int m3k_stage2(jz_usbdev* dev, jz_buffer* buf)
|
||||
static int run_stage2(jz_usbdev* dev, jz_buffer* buf)
|
||||
{
|
||||
int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data);
|
||||
if(rc < 0)
|
||||
|
@ -154,8 +48,8 @@ static int m3k_stage2(jz_usbdev* dev, jz_buffer* buf)
|
|||
return jz_usb_start2(dev, 0x80004000);
|
||||
}
|
||||
|
||||
static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
|
||||
bool decompress, jz_buffer** buf)
|
||||
static int get_file(jz_context* jz, mtar_t* tar, const char* file,
|
||||
bool decompress, jz_buffer** buf)
|
||||
{
|
||||
jz_buffer* buffer = NULL;
|
||||
mtar_header_t h;
|
||||
|
@ -180,7 +74,7 @@ static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
|
|||
|
||||
if(decompress) {
|
||||
uint32_t dst_len;
|
||||
jz_buffer* nbuf = ucl_unpack(buffer->data, buffer->size, &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);
|
||||
|
@ -196,7 +90,7 @@ static int m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
|
|||
return JZ_SUCCESS;
|
||||
}
|
||||
|
||||
static int m3k_show_version(jz_context* jz, jz_buffer* info_file)
|
||||
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;
|
||||
|
@ -211,17 +105,28 @@ static int m3k_show_version(jz_context* jz, jz_buffer* info_file)
|
|||
return JZ_SUCCESS;
|
||||
}
|
||||
|
||||
/** \brief Load the Rockbox bootloader on the FiiO M3K
|
||||
/** \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.m3k" file
|
||||
* \param filename Path to the "bootloader.target" file
|
||||
* \return either JZ_SUCCESS or an error code
|
||||
*/
|
||||
int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
|
||||
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, "r");
|
||||
if(rc != MTAR_ESUCCESS) {
|
||||
jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
|
||||
|
@ -229,25 +134,25 @@ int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
|
|||
}
|
||||
|
||||
/* Extract all necessary files */
|
||||
rc = m3k_get_file(dev->jz, &tar, "spl.m3k", false, &spl);
|
||||
rc = get_file(dev->jz, &tar, spl_filename, false, &spl);
|
||||
if(rc != JZ_SUCCESS)
|
||||
goto error;
|
||||
|
||||
rc = m3k_get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
|
||||
rc = get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
|
||||
if(rc != JZ_SUCCESS)
|
||||
goto error;
|
||||
|
||||
rc = m3k_get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
|
||||
rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
|
||||
if(rc != JZ_SUCCESS)
|
||||
goto error;
|
||||
|
||||
/* Display the version string */
|
||||
rc = m3k_show_version(dev->jz, info_file);
|
||||
rc = show_version(dev->jz, info_file);
|
||||
if(rc != JZ_SUCCESS)
|
||||
goto error;
|
||||
|
||||
/* Stage1 boot of SPL to set up hardware */
|
||||
rc = m3k_stage1(dev, spl);
|
||||
rc = run_stage1(dev, spl);
|
||||
if(rc != JZ_SUCCESS)
|
||||
goto error;
|
||||
|
||||
|
@ -256,7 +161,7 @@ int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
|
|||
|
||||
/* Stage2 boot into the bootloader's recovery menu
|
||||
* User has to take manual action from there */
|
||||
rc = m3k_stage2(dev, bootloader);
|
||||
rc = run_stage2(dev, bootloader);
|
||||
if(rc != JZ_SUCCESS)
|
||||
goto error;
|
||||
|
Loading…
Reference in New Issue