jztool: add support for Shanling Q1 and Eros Q

Change-Id: I8e93162d1a9d82e9d132219f2803b1856d24ae6c
This commit is contained in:
Aidan MacDonald 2021-07-17 19:35:09 +01:00
parent 6f042e91dd
commit 740a50687f
9 changed files with 409 additions and 226 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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