jztool: Support new M3K bootloader

Change-Id: Ia2d96893a9a5c77deb71c1fe32ae5a0585093f5b
This commit is contained in:
Aidan MacDonald 2021-05-11 13:25:26 +01:00
parent 3748117ee3
commit cc22df198d
12 changed files with 471 additions and 806 deletions

View file

@ -5,7 +5,7 @@
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
CFLAGS += -Wall -Wextra -Iinclude
CFLAGS += -Wall -Wextra -Iinclude -I../../tools/ucl/include -I../../lib/microtar/src
OUTPUT = jztool
ifdef RELEASE
@ -15,10 +15,9 @@ CFLAGS += -O0 -ggdb
endif
LIBSOURCES := src/buffer.c src/context.c src/device_info.c \
src/fiiom3k.c src/identify_file.c src/paramlist.c \
src/usb.c src/x1000.c
src/identify_file.c src/fiiom3k.c src/usb.c
SOURCES := $(LIBSOURCES) jztool.c
EXTRADEPS :=
EXTRADEPS := libucl.a libmicrotar.a
CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -)
@ -27,6 +26,7 @@ ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32)
else
ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE)
# OSX -- /opt location is cheesy attempt to support ARM macs
# COMPLETELY UNTESTED, testing from someone with an actual Mac is appreciated!
CFLAGS += -I/usr/local/include -I/opt/homebrew/include
LDOPTS += -L/usr/local/lib -L/opt/homebrew/lib -lusb-1.0
else

View file

@ -1,28 +1,90 @@
# jztool -- Ingenic device utility & bootloader installer
The `jztool` utility can install, backup, and restore the bootloader on
The `jztool` utility can help install, backup, and restore the bootloader on
Rockbox players based on a supported Ingenic SoC.
## FiiO M3K
To use `jztool` on the FiiO M3K you have to connect the player to your
computer in USB boot mode.
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).
The easiest way to do this is by plugging in the microUSB cable to the M3K
and holding the volume down button while plugging the USB into your computer.
If you entered USB boot mode, the button light will turn on but the LCD will
turn off.
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 install or update the Rockbox bootloader on the M3K, use the command
`jztool fiiom3k install`. It is recommended that you take a backup of your
current bootloader so you can restore it in case of any problems.
Copy the `bootloader.m3k` next to the `jztool` executable and follow the
instructions below which are appropriate to your OS.
After any operation finishes, you will have to force a power off of the M3K
by holding down the power button for at least 10 seconds. This must be done
whether the operation succeeds or fails. Just don't power off or unplug the
device in the middle of an operation -- that might make bad things happen.
### Running jztool
#### 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 the M3K 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 -- 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.
```
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
```
### Further instructions
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.
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".
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.
### 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.
See `jztool --help` for info.
## TODO list
@ -38,23 +100,11 @@ Some of the error messages could be friendlier too.
Adding support to the Rockbox utility should be mostly boilerplate since the
jztool library wraps all the troublesome details.
Getting appropriate privileges to access the USB device is the main issue.
Preferably, the Rockbox utility should not run as root/admin/etc.
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.
- Windows: not sure
- Linux: needs udev rules or root privileges
- Mac: apparently does not need privileges
### Porting to Windows
Windows wants to see a driver installed before we can access the USB device,
the easiest way to do this is by having the user run Zadig, a 3rd party app
which can install the WinUSB driver. WinUSB itself is from Microsoft and
bundled with Windows.
Zadig's homepage: https://zadig.akeo.ie/
### Porting to Mac
According to the libusb wiki, libusb works on Mac without any special setup or
privileges, presumably porting there is easy.
On Windows and Mac, no special permissions are needed to access USB devices
assuming the drivers are set up. (Zadig does require administrator access
to run, but that's external to the Rockbox utility.)

View file

@ -37,7 +37,6 @@ typedef struct jz_context jz_context;
typedef struct jz_usbdev jz_usbdev;
typedef struct jz_device_info jz_device_info;
typedef struct jz_buffer jz_buffer;
typedef struct jz_paramlist jz_paramlist;
typedef enum jz_error jz_error;
typedef enum jz_identify_error jz_identify_error;
@ -46,7 +45,6 @@ typedef enum jz_device_type jz_device_type;
typedef enum jz_cpu_type jz_cpu_type;
typedef void(*jz_log_cb)(jz_log_level, const char*);
typedef int(*jz_device_action_fn)(jz_context*, jz_paramlist*);
enum jz_error {
JZ_SUCCESS = 0,
@ -92,10 +90,6 @@ struct jz_device_info {
jz_cpu_type cpu_type;
uint16_t vendor_id;
uint16_t product_id;
int num_actions;
const char* const* action_names;
const jz_device_action_fn* action_funcs;
const char* const* const* action_params;
};
struct jz_buffer {
@ -132,7 +126,6 @@ const jz_device_info* jz_get_device_info_indexed(int index);
int jz_identify_x1000_spl(const void* data, size_t len);
int jz_identify_scramble_image(const void* data, size_t len);
int jz_identify_fiiom3k_bootimage(const void* data, size_t len);
/******************************************************************************
* USB boot ROM protocol
@ -148,27 +141,10 @@ int jz_usb_start2(jz_usbdev* dev, uint32_t addr);
int jz_usb_flush_caches(jz_usbdev* dev);
/******************************************************************************
* X1000 flash protocol
* Rockbox loader (all functions are model-specific, see docs)
*/
int jz_x1000_setup(jz_usbdev* dev, size_t spl_len, const void* spl_data);
int jz_x1000_read_flash(jz_usbdev* dev, uint32_t addr, size_t len, void* data);
int jz_x1000_write_flash(jz_usbdev* dev, uint32_t addr, size_t len, const void* data);
int jz_x1000_boot_rockbox(jz_usbdev* dev);
/******************************************************************************
* FiiO M3K bootloader backup/installation
*/
int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr);
int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf);
int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size,
const void* spl_buf, size_t spl_size,
const void* boot_buf, size_t boot_size);
int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl);
int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl);
int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl);
int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename);
/******************************************************************************
* Simple buffer API
@ -180,15 +156,6 @@ void jz_buffer_free(jz_buffer* buf);
int jz_buffer_load(jz_buffer** buf, const char* filename);
int jz_buffer_save(jz_buffer* buf, const char* filename);
/******************************************************************************
* Parameter list
*/
jz_paramlist* jz_paramlist_new(void);
void jz_paramlist_free(jz_paramlist* pl);
int jz_paramlist_set(jz_paramlist* pl, const char* param, const char* value);
const char* jz_paramlist_get(jz_paramlist* pl, const char* param);
/******************************************************************************
* END
*/

View file

@ -26,21 +26,68 @@
#include <stdbool.h>
jz_context* jz = NULL;
jz_usbdev* usbdev = NULL;
const jz_device_info* dev_info = NULL;
int dev_action = -1;
jz_paramlist* action_params = NULL;
void usage_fiiom3k(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");
exit(4);
}
int cmdline_fiiom3k(int argc, char** argv)
{
if(argc < 2 || strcmp(argv[0], "load")) {
usage_fiiom3k();
return 2;
}
int rc = jz_usb_open(jz, &usbdev, dev_info->vendor_id, dev_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]);
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> <action> [action options]\n"
" jztool [global options] <device> <command> [command arguments]\n"
"\n"
"Global options:\n"
"\n"
" -h, --help Display this help\n"
" -q, --quiet Don't log anything except errors\n"
" -v, --verbose Display detailed logging output\n"
" -l, --loglevel LEVEL Set log level\n");
" -v, --verbose Display detailed logging output\n\n");
printf("Supported devices:\n\n");
int n = jz_get_num_device_info();
@ -49,39 +96,17 @@ void usage(void)
printf(" %s - %s\n", info->name, info->description);
}
printf(
"\n"
"Available actions for fiiom3k:\n"
"\n"
" install --spl <spl.m3k> --bootloader <bootloader.m3k>\n"
" [--without-backup yes] [--backup IMAGE]\n"
" Install or update the Rockbox bootloader on a device.\n"
"\n"
" If --backup is given, back up the current bootloader to IMAGE before\n"
" installing the new bootloader. The installer will normally refuse to\n"
" overwrite your current bootloader; pass '--without-backup yes' if you\n"
" really want to proceed without taking a backup.\n"
"\n"
" WARNING: it is NOT RECOMMENDED to install the Rockbox bootloader\n"
" without taking a backup of the original firmware bootloader. It may\n"
" be very difficult or impossible to recover your player without one.\n"
" At least one M3Ks is known to not to work with the Rockbox bootloader,\n"
" so it is very important to take a backup.\n"
"\n"
" backup --image IMAGE\n"
" Backup the current bootloader to the file IMAGE\n"
"\n"
" restore --image IMAGE\n"
" Restore a bootloader image backup from the file IMAGE\n"
"\n");
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(action_params)
jz_paramlist_free(action_params);
if(usbdev)
jz_usb_close(usbdev);
if(jz)
jz_context_destroy(jz);
}
@ -157,71 +182,14 @@ int main(int argc, char** argv)
exit(2);
}
/* Read the action */
/* Dispatch to device handler */
--argc, ++argv;
if(argc == 0) {
jz_log(jz, JZ_LOG_ERROR, "No action specified (try jztool --help)");
exit(2);
switch(dev_info->device_type) {
case JZ_DEVICE_FIIOM3K:
return cmdline_fiiom3k(argc, argv);
default:
jz_log(jz, JZ_LOG_ERROR, "INTERNAL ERROR: unhandled device type");
return 1;
}
for(dev_action = 0; dev_action < dev_info->num_actions; ++dev_action)
if(!strcmp(*argv, dev_info->action_names[dev_action]))
break;
if(dev_action == dev_info->num_actions) {
jz_log(jz, JZ_LOG_ERROR, "Unknown action '%s' (try jztool --help)", *argv);
exit(2);
}
/* Parse the action options */
action_params = jz_paramlist_new();
if(!action_params) {
jz_log(jz, JZ_LOG_ERROR, "Out of memory: can't create paramlist");
exit(1);
}
const char* const* allowed_params = dev_info->action_params[dev_action];
--argc, ++argv;
while(argc > 0 && argv[0][0] == '-') {
if(argv[0][1] != '-') {
jz_log(jz, JZ_LOG_ERROR, "Invalid option '%s' for action", *argv);
exit(2);
}
bool bad_option = true;
for(int i = 0; allowed_params[i] != NULL; ++i) {
if(!strcmp(&argv[0][2], allowed_params[i])) {
++argv;
if(--argc == 0) {
jz_log(jz, JZ_LOG_ERROR, "Missing argument for parameter '%s'", *argv);
exit(2);
}
int rc = jz_paramlist_set(action_params, allowed_params[i], *argv);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Out of memory");
exit(1);
}
bad_option = false;
}
}
if(bad_option) {
jz_log(jz, JZ_LOG_ERROR, "Invalid option '%s' for action", *argv);
exit(2);
}
--argc, ++argv;
}
if(argc != 0) {
jz_log(jz, JZ_LOG_ERROR, "Excess arguments on command line");
exit(2);
}
/* Invoke action handler */
int rc = dev_info->action_funcs[dev_action](jz, action_params);
return (rc < 0) ? 1 : 0;
}

View file

@ -27,6 +27,10 @@
#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
*/
@ -137,13 +141,18 @@ void jz_log_cb_stderr(jz_log_level lev, const char* msg)
*/
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) {
@ -158,6 +167,7 @@ int jz_context_ref_libusb(jz_context* jz)
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) {

View file

@ -22,30 +22,6 @@
#include "jztool.h"
#include <string.h>
static const char* const fiiom3k_action_names[] = {
"install",
"backup",
"restore",
};
static const char* const fiiom3k_install_action_params[] =
{"spl", "bootloader", "backup", "without-backup", NULL};
static const char* const fiiom3k_backuprestore_action_params[] =
{"spl", "image", NULL};
static const char* const* fiiom3k_action_params[] = {
fiiom3k_install_action_params,
fiiom3k_backuprestore_action_params,
fiiom3k_backuprestore_action_params,
};
static const jz_device_action_fn fiiom3k_action_funcs[] = {
jz_fiiom3k_install,
jz_fiiom3k_backup,
jz_fiiom3k_restore,
};
static const jz_device_info infotable[] = {
{
.name = "fiiom3k",
@ -54,10 +30,6 @@ static const jz_device_info infotable[] = {
.cpu_type = JZ_CPU_X1000,
.vendor_id = 0xa108,
.product_id = 0x1000,
.num_actions = sizeof(fiiom3k_action_names)/sizeof(void*),
.action_names = fiiom3k_action_names,
.action_funcs = fiiom3k_action_funcs,
.action_params = fiiom3k_action_params,
},
};
@ -69,6 +41,7 @@ int jz_get_num_device_info(void)
return infotable_size;
}
/** \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)

View file

@ -20,264 +20,255 @@
****************************************************************************/
#include "jztool.h"
#include "jztool_private.h"
#include "microtar.h"
#include "ucl/ucl.h"
#include <stdbool.h>
#include <string.h>
#define IMAGE_ADDR 0
#define IMAGE_SIZE (128 * 1024)
#define SPL_OFFSET 0
#define SPL_SIZE (12 * 1024)
#define BOOT_OFFSET (26 * 1024)
#define BOOT_SIZE (102 * 1024)
int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr)
static uint32_t xread32(const uint8_t* d)
{
jz_buffer* buf = jz_buffer_alloc(IMAGE_SIZE, NULL);
if(!buf)
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)
{
int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data);
if(rc < 0)
return rc;
return jz_usb_start1(dev, 0xf4001800);
}
static int m3k_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 m3k_get_file(jz_context* jz, mtar_t* tar, const char* file,
bool decompress, jz_buffer** buf)
{
jz_buffer* buffer = NULL;
mtar_header_t h;
int rc;
rc = mtar_find(tar, file, &h);
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;
}
buffer = jz_buffer_alloc(h.size, NULL);
if(!buffer)
return JZ_ERR_OUT_OF_MEMORY;
int rc = jz_x1000_read_flash(dev, IMAGE_ADDR, buf->size, buf->data);
if(rc < 0) {
jz_buffer_free(buf);
return rc;
}
*bufptr = buf;
return JZ_SUCCESS;
}
int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf)
{
int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size);
if(rc < 0 || image_size != IMAGE_SIZE)
return JZ_ERR_BAD_FILE_FORMAT;
rc = jz_x1000_write_flash(dev, IMAGE_ADDR, image_size, image_buf);
if(rc < 0)
return rc;
return JZ_SUCCESS;
}
int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size,
const void* spl_buf, size_t spl_size,
const void* boot_buf, size_t boot_size)
{
int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Boot image is invalid: %d", rc);
rc = mtar_read_data(tar, buffer->data, buffer->size);
if(rc != MTAR_ESUCCESS) {
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;
}
rc = jz_identify_x1000_spl(spl_buf, spl_size);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "SPL image is invalid: %d", rc);
return JZ_ERR_BAD_FILE_FORMAT;
}
if(spl_size > SPL_SIZE) {
jz_log(jz, JZ_LOG_ERROR, "SPL is too big");
return JZ_ERR_BAD_FILE_FORMAT;
}
rc = jz_identify_scramble_image(boot_buf, boot_size);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Bootloader image is invalid: %d", rc);
return JZ_ERR_BAD_FILE_FORMAT;
}
if(boot_size > BOOT_SIZE) {
jz_log(jz, JZ_LOG_ERROR, "Bootloader is too big");
return JZ_ERR_BAD_FILE_FORMAT;
}
uint8_t* imgdat = (uint8_t*)image_buf;
memset(&imgdat[SPL_OFFSET], 0xff, SPL_SIZE);
memcpy(&imgdat[SPL_OFFSET], spl_buf, spl_size);
memset(&imgdat[BOOT_OFFSET], 0xff, BOOT_SIZE);
memcpy(&imgdat[BOOT_OFFSET], boot_buf, boot_size);
return JZ_SUCCESS;
}
#define IMGBUF 0
#define SPLBUF 1
#define BOOTBUF 2
#define NUMBUFS 3
#define IMGBUF_NAME "image"
#define SPLBUF_NAME "spl"
#define BOOTBUF_NAME "bootloader"
#define FIIOM3K_INIT_WORKSTATE {0}
struct fiiom3k_workstate {
jz_usbdev* dev;
jz_buffer* bufs[NUMBUFS];
};
static void fiiom3k_action_cleanup(struct fiiom3k_workstate* state)
{
for(int i = 0; i < NUMBUFS; ++i)
if(state->bufs[i])
jz_buffer_free(state->bufs[i]);
if(state->dev)
jz_usb_close(state->dev);
}
static int fiiom3k_action_loadbuf(jz_context* jz, jz_paramlist* pl,
struct fiiom3k_workstate* state, int idx)
{
const char* const paramnames[] = {IMGBUF_NAME, SPLBUF_NAME, BOOTBUF_NAME};
if(state->bufs[idx])
return JZ_SUCCESS;
const char* filename = jz_paramlist_get(pl, paramnames[idx]);
if(!filename) {
jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", paramnames[idx]);
return JZ_ERR_OTHER;
}
int rc = jz_buffer_load(&state->bufs[idx], filename);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Error reading '%s' file (%d): %s", paramnames[idx], rc, filename);
return rc;
}
return JZ_SUCCESS;
}
static int fiiom3k_action_setup(jz_context* jz, jz_paramlist* pl,
struct fiiom3k_workstate* state)
{
const jz_device_info* info = jz_get_device_info(JZ_DEVICE_FIIOM3K);
if(!info)
return JZ_ERR_OTHER;
int rc = fiiom3k_action_loadbuf(jz, pl, state, SPLBUF);
if(rc < 0)
return rc;
jz_log(jz, JZ_LOG_DETAIL, "Open USB device %04x:%04x",
(unsigned int)info->vendor_id, (unsigned int)info->product_id);
rc = jz_usb_open(jz, &state->dev, info->vendor_id, info->product_id);
if(rc < 0)
return rc;
jz_log(jz, JZ_LOG_DETAIL, "Setup device for flash access");
jz_buffer* splbuf = state->bufs[SPLBUF];
return jz_x1000_setup(state->dev, splbuf->size, splbuf->data);
}
int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl)
{
struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE;
int rc;
rc = fiiom3k_action_loadbuf(jz, pl, &state, BOOTBUF);
if(rc < 0)
goto error;
rc = fiiom3k_action_setup(jz, pl, &state);
if(rc < 0)
goto error;
jz_log(jz, JZ_LOG_DETAIL, "Reading boot image from device");
rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]);
if(rc < 0)
goto error;
jz_buffer* img_buf = state.bufs[IMGBUF];
const char* backupfile = jz_paramlist_get(pl, "backup");
const char* without_backup = jz_paramlist_get(pl, "without-backup");
if(backupfile) {
jz_log(jz, JZ_LOG_DETAIL, "Backup original boot image to file: %s", backupfile);
rc = jz_buffer_save(img_buf, backupfile);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Error saving backup image file (%d): %s", rc, backupfile);
goto error;
if(decompress) {
uint32_t dst_len;
jz_buffer* nbuf = 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;
}
} else if(!without_backup || strcmp(without_backup, "yes")) {
jz_log(jz, JZ_LOG_ERROR, "No --backup option given and --without-backup yes not specified");
jz_log(jz, JZ_LOG_ERROR, "Refusing to flash a new bootloader without taking a backup");
goto error;
/* for simplicity just forget original size of buffer */
nbuf->size = dst_len;
buffer = nbuf;
}
jz_log(jz, JZ_LOG_DETAIL, "Patching image with new SPL/bootloader");
jz_buffer* boot_buf = state.bufs[BOOTBUF];
jz_buffer* spl_buf = state.bufs[SPLBUF];
rc = jz_fiiom3k_patchboot(jz, img_buf->data, img_buf->size,
spl_buf->data, spl_buf->size,
boot_buf->data, boot_buf->size);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Error patching image: %d", rc);
goto error;
}
jz_log(jz, JZ_LOG_DETAIL, "Writing patched image to device");
rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data);
if(rc < 0)
goto error;
rc = JZ_SUCCESS;
error:
fiiom3k_action_cleanup(&state);
return rc;
*buf = buffer;
return JZ_SUCCESS;
}
int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl)
static int m3k_show_version(jz_context* jz, jz_buffer* info_file)
{
struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE;
/* 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 the FiiO M3K
* \param dev USB device freshly returned by jz_usb_open()
* \param filename Path to the "bootloader.m3k" file
* \return either JZ_SUCCESS or an error code
*/
int jz_fiiom3k_boot(jz_usbdev* dev, const char* filename)
{
jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
mtar_t tar;
int rc;
const char* outfile_path = jz_paramlist_get(pl, IMGBUF_NAME);
if(!outfile_path) {
jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", IMGBUF_NAME);
rc = JZ_ERR_OTHER;
goto error;
rc = 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);
return JZ_ERR_OPEN_FILE;
}
rc = fiiom3k_action_setup(jz, pl, &state);
if(rc < 0)
/* Extract all necessary files */
rc = m3k_get_file(dev->jz, &tar, "spl.m3k", false, &spl);
if(rc != JZ_SUCCESS)
goto error;
rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]);
if(rc < 0)
rc = m3k_get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
if(rc != JZ_SUCCESS)
goto error;
rc = jz_buffer_save(state.bufs[IMGBUF], outfile_path);
if(rc < 0) {
jz_log(jz, JZ_LOG_ERROR, "Error writing '%s' file (%d): %s", IMGBUF_NAME, rc, outfile_path);
goto error;
}
rc = JZ_SUCCESS;
error:
fiiom3k_action_cleanup(&state);
return rc;
}
int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl)
{
struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE;
int rc;
rc = fiiom3k_action_loadbuf(jz, pl, &state, IMGBUF);
if(rc < 0)
rc = m3k_get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
if(rc != JZ_SUCCESS)
goto error;
rc = fiiom3k_action_setup(jz, pl, &state);
if(rc < 0)
/* Display the version string */
rc = m3k_show_version(dev->jz, info_file);
if(rc != JZ_SUCCESS)
goto error;
jz_buffer* img_buf = state.bufs[IMGBUF];
rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data);
if(rc < 0)
/* Stage1 boot of SPL to set up hardware */
rc = m3k_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 = m3k_stage2(dev, bootloader);
if(rc != JZ_SUCCESS)
goto error;
rc = JZ_SUCCESS;
error:
fiiom3k_action_cleanup(&state);
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;
}

View file

@ -81,6 +81,14 @@ static uint8_t crc7(const uint8_t* buf, size_t len)
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,
@ -115,6 +123,14 @@ static const struct scramble_model_info {
{NULL, 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 */
@ -143,37 +159,3 @@ int jz_identify_scramble_image(const void* data, size_t len)
return JZ_SUCCESS;
}
int jz_identify_fiiom3k_bootimage(const void* data, size_t len)
{
/* The bootloader image is simply a dump of the first NAND eraseblock,
* so it has a fixed 128 KiB size */
if(len != 128*1024)
return JZ_IDERR_WRONG_SIZE;
/* We'll verify the embedded SPL, but we have to drag out the correct
* length from the header. Length should be more than 12 KiB, due to
* limitations of the hardware */
const struct x1000_spl_header* spl_header;
spl_header = (const struct x1000_spl_header*)data;
if(spl_header->length > 12 * 1024)
return JZ_IDERR_BAD_HEADER;
int rc = jz_identify_x1000_spl(data, spl_header->length);
if(rc < 0)
return rc;
const uint8_t* dat = (const uint8_t*)data;
/* Check the partition table is present */
if(memcmp(&dat[0x3c00], "nand", 4))
return JZ_IDERR_OTHER;
/* Check first bytes of PDMA firmware. It doesn't change
* between OF versions, and Rockbox doesn't modify it. */
static const uint8_t pdma_fw[] = {0x54, 0x25, 0x42, 0xb3, 0x70, 0x25, 0x42, 0xb3};
if(memcmp(&dat[0x4000], pdma_fw, sizeof(pdma_fw)))
return JZ_IDERR_OTHER;
return JZ_SUCCESS;
}

View file

@ -1,135 +0,0 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "jztool.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
struct jz_paramlist {
int size;
char** keys;
char** values;
};
static int jz_paramlist_extend(jz_paramlist* pl, int count)
{
int nsize = pl->size + count;
/* Reallocate key list */
char** nkeys = realloc(pl->keys, nsize * sizeof(char*));
if(!nkeys)
return JZ_ERR_OUT_OF_MEMORY;
for(int i = pl->size; i < nsize; ++i)
nkeys[i] = NULL;
pl->keys = nkeys;
/* Reallocate value list */
char** nvalues = realloc(pl->values, nsize * sizeof(char*));
if(!nvalues)
return JZ_ERR_OUT_OF_MEMORY;
for(int i = pl->size; i < nsize; ++i)
nvalues[i] = NULL;
pl->values = nvalues;
pl->size = nsize;
return JZ_SUCCESS;
}
jz_paramlist* jz_paramlist_new(void)
{
jz_paramlist* pl = malloc(sizeof(struct jz_paramlist));
if(!pl)
return NULL;
pl->size = 0;
pl->keys = NULL;
pl->values = NULL;
return pl;
}
void jz_paramlist_free(jz_paramlist* pl)
{
for(int i = 0; i < pl->size; ++i) {
free(pl->keys[i]);
free(pl->values[i]);
}
if(pl->size > 0) {
free(pl->keys);
free(pl->values);
}
free(pl);
}
int jz_paramlist_set(jz_paramlist* pl, const char* param, const char* value)
{
int pos = -1;
for(int i = 0; i < pl->size; ++i) {
if(!pl->keys[i] || !strcmp(pl->keys[i], param)) {
pos = i;
break;
}
}
if(pos == -1) {
pos = pl->size;
int rc = jz_paramlist_extend(pl, 1);
if(rc < 0)
return rc;
}
bool need_key = (pl->keys[pos] == NULL);
if(need_key) {
char* newparam = strdup(param);
if(!newparam)
return JZ_ERR_OUT_OF_MEMORY;
pl->keys[pos] = newparam;
}
char* newvalue = strdup(value);
if(!newvalue) {
if(need_key) {
free(pl->keys[pos]);
pl->keys[pos] = NULL;
}
return JZ_ERR_OUT_OF_MEMORY;
}
pl->values[pos] = newvalue;
return JZ_SUCCESS;
}
const char* jz_paramlist_get(jz_paramlist* pl, const char* param)
{
for(int i = 0; i < pl->size; ++i)
if(pl->keys[i] && !strcmp(pl->keys[i], param))
return pl->values[i];
return NULL;
}

View file

@ -30,6 +30,16 @@
#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;
@ -80,7 +90,7 @@ int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t p
}
if(dev_index < 0) {
jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%05x found",
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;
@ -100,6 +110,8 @@ int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t p
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;
@ -119,14 +131,20 @@ int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t p
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)
{
int rc = libusb_control_transfer(dev->handle,
@ -137,12 +155,24 @@ static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg)
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;
@ -156,12 +186,16 @@ static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf)
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)
{
@ -177,26 +211,54 @@ static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr,
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);
}
/** \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);
}
/** \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);

View file

@ -1,209 +0,0 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "jztool_private.h"
#include "../../../firmware/target/mips/ingenic_x1000/spl-x1000-defs.h"
#include "../../../firmware/target/mips/ingenic_x1000/nand-x1000-err.h"
#include <string.h>
static uint32_t to_le32(uint32_t x)
{
union { uint32_t u; uint8_t p[4]; } f;
f.p[0] = x & 0xff;
f.p[1] = (x >> 8) & 0xff;
f.p[2] = (x >> 16) & 0xff;
f.p[3] = (x >> 24) & 0xff;
return f.u;
}
static uint32_t from_le32(uint32_t x)
{
union { uint32_t u; uint8_t p[4]; } f;
f.u = x;
return f.p[0] | (f.p[1] << 8) | (f.p[2] << 16) | (f.p[3] << 24);
}
static const char* jz_x1000_nand_strerror(int rc)
{
switch(rc) {
case NANDERR_CHIP_UNSUPPORTED:
return "Chip unsupported";
case NANDERR_WRITE_PROTECTED:
return "Operation forbidden by write-protect";
case NANDERR_UNALIGNED_ADDRESS:
return "Improperly aligned address";
case NANDERR_UNALIGNED_LENGTH:
return "Improperly aligned length";
case NANDERR_READ_FAILED:
return "Read operation failed";
case NANDERR_ECC_FAILED:
return "Uncorrectable ECC error on read";
case NANDERR_ERASE_FAILED:
return "Erase operation failed";
case NANDERR_PROGRAM_FAILED:
return "Program operation failed";
case NANDERR_COMMAND_FAILED:
return "NAND command failed";
default:
return "Unknown NAND error";
}
}
static int jz_x1000_send_args(jz_usbdev* dev, struct x1000_spl_arguments* args)
{
args->command = to_le32(args->command);
args->param1 = to_le32(args->param1);
args->param2 = to_le32(args->param2);
args->flags = to_le32(args->flags);
return jz_usb_send(dev, SPL_ARGUMENTS_ADDRESS, sizeof(*args), args);
}
static int jz_x1000_recv_status(jz_usbdev* dev, struct x1000_spl_status* status)
{
int rc = jz_usb_recv(dev, SPL_STATUS_ADDRESS, sizeof(*status), status);
if(rc < 0)
return rc;
status->err_code = from_le32(status->err_code);
status->reserved = from_le32(status->reserved);
return JZ_SUCCESS;
}
int jz_x1000_setup(jz_usbdev* dev, size_t spl_len, const void* spl_data)
{
int rc = jz_identify_x1000_spl(spl_data, spl_len);
if(rc < 0)
return JZ_ERR_BAD_FILE_FORMAT;
if(spl_len > SPL_MAX_SIZE)
return JZ_ERR_BAD_FILE_FORMAT;
rc = jz_usb_send(dev, SPL_LOAD_ADDRESS, spl_len, spl_data);
if(rc < 0)
return rc;
struct x1000_spl_arguments args;
args.command = SPL_CMD_BOOT;
args.param1 = SPL_BOOTOPT_NONE;
args.param2 = 0;
args.flags = 0;
rc = jz_x1000_send_args(dev, &args);
if(rc < 0)
return rc;
rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS);
if(rc < 0)
return rc;
jz_sleepms(100);
struct x1000_spl_status status;
rc = jz_x1000_recv_status(dev, &status);
if(rc < 0)
return rc;
if(status.err_code != 0) {
jz_log(dev->jz, JZ_LOG_ERROR, "X1000 device init error: %d", status.err_code);
return JZ_ERR_OTHER;
}
return JZ_SUCCESS;
}
int jz_x1000_read_flash(jz_usbdev* dev, uint32_t addr, size_t len, void* data)
{
struct x1000_spl_arguments args;
args.command = SPL_CMD_FLASH_READ;
args.param1 = addr;
args.param2 = len;
args.flags = SPL_FLAG_SKIP_INIT;
int rc = jz_x1000_send_args(dev, &args);
if(rc < 0)
return rc;
rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS);
if(rc < 0)
return rc;
jz_sleepms(500);
struct x1000_spl_status status;
rc = jz_x1000_recv_status(dev, &status);
if(rc < 0)
return rc;
if(status.err_code != 0) {
jz_log(dev->jz, JZ_LOG_ERROR, "X1000 flash read error: %s",
jz_x1000_nand_strerror(status.err_code));
return JZ_ERR_FLASH_ERROR;
}
return jz_usb_recv(dev, SPL_BUFFER_ADDRESS, len, data);
}
int jz_x1000_write_flash(jz_usbdev* dev, uint32_t addr, size_t len, const void* data)
{
int rc = jz_usb_send(dev, SPL_BUFFER_ADDRESS, len, data);
if(rc < 0)
return rc;
struct x1000_spl_arguments args;
args.command = SPL_CMD_FLASH_WRITE;
args.param1 = addr;
args.param2 = len;
args.flags = SPL_FLAG_SKIP_INIT;
rc = jz_x1000_send_args(dev, &args);
if(rc < 0)
return rc;
rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS);
if(rc < 0)
return rc;
jz_sleepms(500);
struct x1000_spl_status status;
rc = jz_x1000_recv_status(dev, &status);
if(rc < 0)
return rc;
if(status.err_code != 0) {
jz_log(dev->jz, JZ_LOG_ERROR, "X1000 flash write error: %s",
jz_x1000_nand_strerror(status.err_code));
return JZ_ERR_FLASH_ERROR;
}
return JZ_SUCCESS;
}
int jz_x1000_boot_rockbox(jz_usbdev* dev)
{
struct x1000_spl_arguments args;
args.command = SPL_CMD_BOOT;
args.param1 = SPL_BOOTOPT_ROCKBOX;
args.param2 = 0;
args.flags = 0;
int rc = jz_x1000_send_args(dev, &args);
if(rc < 0)
return rc;
return jz_usb_start1(dev, SPL_EXEC_ADDRESS);
}

View file

@ -118,6 +118,12 @@ $(LIBBZIP2): $(OBJDIR)$(LIBBZIP2)
$(OBJDIR)$(LIBBZIP2):
$(SILENT)$(MAKE) -C $(TOP)/bzip2 TARGET_DIR=$(OBJDIR) CC=$(CC) $@
LIBMICROTAR = libmicrotar.a
$(LIBMICROTAR): $(OBJDIR)$(LIBMICROTAR)
$(OBJDIR)$(LIBMICROTAR):
$(SILENT)$(MAKE) -C $(TOP)/../lib/microtar/src TARGET_DIR=$(OBJDIR) CC=$(CC) $@
# building the standalone executable
$(BINARY): $(OBJS) $(EXTRADEPS) $(addprefix $(OBJDIR),$(EXTRALIBOBJS)) $(TARGET_DIR)lib$(OUTPUT).a
$(info LD $@)