jztool: Support new M3K bootloader
Change-Id: Ia2d96893a9a5c77deb71c1fe32ae5a0585093f5b
This commit is contained in:
parent
3748117ee3
commit
cc22df198d
12 changed files with 471 additions and 806 deletions
|
@ -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
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 $@)
|
||||
|
|
Loading…
Reference in a new issue