mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 10:07:38 -04:00
jztool: New utility for installing a bootloader on FiiO M3K
At present, this is just a command line tool for Linux only. Integrating this with the Rockbox utility and porting to other platforms should be straightforward; the README contains more information. Change-Id: Ie66fc837a02ab13c878925360cabc9805597548a
This commit is contained in:
parent
1b8542490d
commit
b41d53792c
14 changed files with 1961 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -97,6 +97,7 @@ __pycache__
|
|||
/rbutil/mknwzboot/mknwzboot
|
||||
/rbutil/mkzenboot/mkzenboot
|
||||
/rbutil/sansapatcher/sansapatcher
|
||||
/rbutil/jztool/jztool
|
||||
/rbutil/tools/bin2c
|
||||
|
||||
# /tools/
|
||||
|
|
37
rbutil/jztool/Makefile
Normal file
37
rbutil/jztool/Makefile
Normal file
|
@ -0,0 +1,37 @@
|
|||
# __________ __ ___.
|
||||
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
# \/ \/ \/ \/ \/
|
||||
|
||||
CFLAGS += -Wall -Wextra -Iinclude
|
||||
OUTPUT = jztool
|
||||
|
||||
ifdef RELEASE
|
||||
CFLAGS += -Os -DNDEBUG
|
||||
else
|
||||
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
|
||||
SOURCES := $(LIBSOURCES) jztool.c
|
||||
EXTRADEPS :=
|
||||
|
||||
CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -)
|
||||
|
||||
ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32)
|
||||
# TODO: support Windows
|
||||
else
|
||||
ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE)
|
||||
# TODO: support OSX
|
||||
else
|
||||
# Linux
|
||||
CFLAGS += `pkg-config --cflags libusb-1.0`
|
||||
LDOPTS += `pkg-config --libs libusb-1.0`
|
||||
endif
|
||||
endif
|
||||
|
||||
include ../libtools.make
|
60
rbutil/jztool/README.md
Normal file
60
rbutil/jztool/README.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# jztool -- Ingenic device utility & bootloader installer
|
||||
|
||||
The `jztool` utility can 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
See `jztool --help` for info.
|
||||
|
||||
## TODO list
|
||||
|
||||
### Add better documentation and logging
|
||||
|
||||
There's only a bare minimum of documentation, and logging is sparse, not
|
||||
really enough to debug problems.
|
||||
|
||||
Some of the error messages could be friendlier too.
|
||||
|
||||
### Integration with the Rockbox utility
|
||||
|
||||
Adding support to the Rockbox utility should be mostly boilerplate since the
|
||||
jztool library wraps all the troublesome details.
|
||||
|
||||
Getting appropriate privileges to access the USB device is the main issue.
|
||||
Preferably, the Rockbox utility should not run as root/admin/etc.
|
||||
|
||||
- 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.
|
200
rbutil/jztool/include/jztool.h
Normal file
200
rbutil/jztool/include/jztool.h
Normal file
|
@ -0,0 +1,200 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef JZTOOL_H
|
||||
#define JZTOOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/******************************************************************************
|
||||
* Types, enumerations, etc
|
||||
*/
|
||||
|
||||
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;
|
||||
typedef enum jz_log_level jz_log_level;
|
||||
typedef enum jz_device_type jz_device_type;
|
||||
typedef enum jz_cpu_type jz_cpu_type;
|
||||
|
||||
typedef void(*jz_log_cb)(jz_log_level, const char*);
|
||||
typedef int(*jz_device_action_fn)(jz_context*, jz_paramlist*);
|
||||
|
||||
enum jz_error {
|
||||
JZ_SUCCESS = 0,
|
||||
JZ_ERR_OUT_OF_MEMORY = -1,
|
||||
JZ_ERR_OPEN_FILE = -2,
|
||||
JZ_ERR_FILE_IO = -3,
|
||||
JZ_ERR_USB = -4,
|
||||
JZ_ERR_NO_DEVICE = -5,
|
||||
JZ_ERR_BAD_FILE_FORMAT = -6,
|
||||
JZ_ERR_FLASH_ERROR = -7,
|
||||
JZ_ERR_OTHER = -99,
|
||||
};
|
||||
|
||||
enum jz_identify_error {
|
||||
JZ_IDERR_OTHER = -1,
|
||||
JZ_IDERR_WRONG_SIZE = -2,
|
||||
JZ_IDERR_BAD_HEADER = -3,
|
||||
JZ_IDERR_BAD_CHECKSUM = -4,
|
||||
JZ_IDERR_UNRECOGNIZED_MODEL = -5,
|
||||
};
|
||||
|
||||
enum jz_log_level {
|
||||
JZ_LOG_IGNORE = -1,
|
||||
JZ_LOG_ERROR = 0,
|
||||
JZ_LOG_WARNING = 1,
|
||||
JZ_LOG_NOTICE = 2,
|
||||
JZ_LOG_DETAIL = 3,
|
||||
JZ_LOG_DEBUG = 4,
|
||||
};
|
||||
|
||||
enum jz_device_type {
|
||||
JZ_DEVICE_FIIOM3K,
|
||||
};
|
||||
|
||||
enum jz_cpu_type {
|
||||
JZ_CPU_X1000,
|
||||
};
|
||||
|
||||
struct jz_device_info {
|
||||
const char* name;
|
||||
const char* description;
|
||||
jz_device_type device_type;
|
||||
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 {
|
||||
size_t size;
|
||||
uint8_t* data;
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* Library context and general functions
|
||||
*/
|
||||
|
||||
jz_context* jz_context_create(void);
|
||||
void jz_context_destroy(jz_context* jz);
|
||||
|
||||
void jz_context_set_user_data(jz_context* jz, void* ptr);
|
||||
void* jz_context_get_user_data(jz_context* jz);
|
||||
|
||||
void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb);
|
||||
void jz_context_set_log_level(jz_context* jz, jz_log_level lev);
|
||||
|
||||
void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...);
|
||||
void jz_log_cb_stderr(jz_log_level lev, const char* msg);
|
||||
|
||||
void jz_sleepms(int ms);
|
||||
|
||||
/******************************************************************************
|
||||
* Device and file info
|
||||
*/
|
||||
|
||||
int jz_get_num_device_info(void);
|
||||
const jz_device_info* jz_get_device_info(jz_device_type type);
|
||||
const jz_device_info* jz_get_device_info_named(const char* name);
|
||||
const jz_device_info* jz_get_device_info_indexed(int index);
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id);
|
||||
void jz_usb_close(jz_usbdev* dev);
|
||||
|
||||
int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data);
|
||||
int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data);
|
||||
int jz_usb_start1(jz_usbdev* dev, uint32_t addr);
|
||||
int jz_usb_start2(jz_usbdev* dev, uint32_t addr);
|
||||
int jz_usb_flush_caches(jz_usbdev* dev);
|
||||
|
||||
/******************************************************************************
|
||||
* X1000 flash protocol
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
/******************************************************************************
|
||||
* Simple buffer API
|
||||
*/
|
||||
|
||||
jz_buffer* jz_buffer_alloc(size_t size, const void* data);
|
||||
void jz_buffer_free(jz_buffer* buf);
|
||||
|
||||
int jz_buffer_load(jz_buffer** buf, const char* filename);
|
||||
int jz_buffer_save(jz_buffer* buf, const char* filename);
|
||||
|
||||
/******************************************************************************
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* JZTOOL_H */
|
227
rbutil/jztool/jztool.c
Normal file
227
rbutil/jztool/jztool.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
jz_context* jz = NULL;
|
||||
const jz_device_info* dev_info = NULL;
|
||||
int dev_action = -1;
|
||||
jz_paramlist* action_params = NULL;
|
||||
|
||||
void usage(void)
|
||||
{
|
||||
printf("Usage:\n"
|
||||
" jztool [global options] <device> <action> [action options]\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");
|
||||
|
||||
printf("Supported devices:\n\n");
|
||||
int n = jz_get_num_device_info();
|
||||
for(int i = 0; i < n; ++i) {
|
||||
const jz_device_info* info = jz_get_device_info_indexed(i);
|
||||
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");
|
||||
|
||||
exit(4);
|
||||
}
|
||||
|
||||
void cleanup(void)
|
||||
{
|
||||
if(action_params)
|
||||
jz_paramlist_free(action_params);
|
||||
if(jz)
|
||||
jz_context_destroy(jz);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc < 2)
|
||||
usage();
|
||||
|
||||
/* Library initialization */
|
||||
jz = jz_context_create();
|
||||
if(!jz) {
|
||||
fprintf(stderr, "ERROR: Can't create context");
|
||||
return 1;
|
||||
}
|
||||
|
||||
atexit(cleanup);
|
||||
jz_context_set_log_cb(jz, jz_log_cb_stderr);
|
||||
jz_context_set_log_level(jz, JZ_LOG_NOTICE);
|
||||
|
||||
/* Parse global options */
|
||||
--argc, ++argv;
|
||||
while(argc > 0 && argv[0][0] == '-') {
|
||||
if(!strcmp(*argv, "-h") || !strcmp(*argv, "--help"))
|
||||
usage();
|
||||
else if(!strcmp(*argv, "-q") || !strcmp(*argv, "--quiet"))
|
||||
jz_context_set_log_level(jz, JZ_LOG_ERROR);
|
||||
else if(!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose"))
|
||||
jz_context_set_log_level(jz, JZ_LOG_DETAIL);
|
||||
else if(!strcmp(*argv, "-l") || !strcmp(*argv, "--loglevel")) {
|
||||
++argv;
|
||||
if(--argc == 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "Missing argument to option %s", *argv);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
enum jz_log_level level;
|
||||
if(!strcmp(*argv, "ignore"))
|
||||
level = JZ_LOG_IGNORE;
|
||||
else if(!strcmp(*argv, "error"))
|
||||
level = JZ_LOG_ERROR;
|
||||
else if(!strcmp(*argv, "warning"))
|
||||
level = JZ_LOG_WARNING;
|
||||
else if(!strcmp(*argv, "notice"))
|
||||
level = JZ_LOG_NOTICE;
|
||||
else if(!strcmp(*argv, "detail"))
|
||||
level = JZ_LOG_DETAIL;
|
||||
else if(!strcmp(*argv, "debug"))
|
||||
level = JZ_LOG_DEBUG;
|
||||
else {
|
||||
jz_log(jz, JZ_LOG_ERROR, "Invalid log level '%s'", *argv);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
jz_context_set_log_level(jz, level);
|
||||
} else {
|
||||
jz_log(jz, JZ_LOG_ERROR, "Invalid global option '%s'", *argv);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
--argc, ++argv;
|
||||
}
|
||||
|
||||
/* Read the device type */
|
||||
if(argc == 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "No device specified (try jztool --help)");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
dev_info = jz_get_device_info_named(*argv);
|
||||
if(!dev_info) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "Unknown device '%s' (try jztool --help)", *argv);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
/* Read the action */
|
||||
--argc, ++argv;
|
||||
if(argc == 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "No action specified (try jztool --help)");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
134
rbutil/jztool/src/buffer.c
Normal file
134
rbutil/jztool/src/buffer.c
Normal file
|
@ -0,0 +1,134 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/** \brief Allocate a buffer, optionally providing its contents.
|
||||
* \param size Number of bytes to allocate
|
||||
* \param data Initial contents of the buffer, must be at least `size` bytes
|
||||
* \return Pointer to buffer or NULL if out of memory.
|
||||
* \note The buffer will not take ownership of the `data` pointer, instead it
|
||||
* allocates a fresh buffer and copies the contents of `data` into it.
|
||||
*/
|
||||
jz_buffer* jz_buffer_alloc(size_t size, const void* data)
|
||||
{
|
||||
jz_buffer* buf = malloc(sizeof(struct jz_buffer));
|
||||
if(!buf)
|
||||
return NULL;
|
||||
|
||||
buf->data = malloc(size);
|
||||
if(!buf->data) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(data)
|
||||
memcpy(buf->data, data, size);
|
||||
|
||||
buf->size = size;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/** \brief Free a buffer
|
||||
*/
|
||||
void jz_buffer_free(jz_buffer* buf)
|
||||
{
|
||||
if(buf) {
|
||||
free(buf->data);
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/** \brief Load a buffer from a file
|
||||
* \param buf Returns loaded buffer on success, unmodified on error
|
||||
* \param filename Path to the file
|
||||
* \return either JZ_SUCCESS, or one of the following errors
|
||||
* \retval JZ_ERR_OPEN_FILE file cannot be opened
|
||||
* \retval JZ_ERR_OUT_OF_MEMORY cannot allocate buffer to hold file contents
|
||||
* \retval JZ_ERR_FILE_IO problem reading file data
|
||||
*/
|
||||
int jz_buffer_load(jz_buffer** buf, const char* filename)
|
||||
{
|
||||
FILE* f;
|
||||
jz_buffer* b;
|
||||
int rc;
|
||||
|
||||
f = fopen(filename, "rb");
|
||||
if(!f)
|
||||
return JZ_ERR_OPEN_FILE;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
int size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
b = jz_buffer_alloc(size, NULL);
|
||||
if(!b) {
|
||||
rc = JZ_ERR_OUT_OF_MEMORY;
|
||||
goto err_fclose;
|
||||
}
|
||||
|
||||
if(fread(b->data, size, 1, f) != 1) {
|
||||
rc = JZ_ERR_FILE_IO;
|
||||
goto err_free_buf;
|
||||
}
|
||||
|
||||
rc = JZ_SUCCESS;
|
||||
*buf = b;
|
||||
|
||||
err_fclose:
|
||||
fclose(f);
|
||||
return rc;
|
||||
|
||||
err_free_buf:
|
||||
jz_buffer_free(b);
|
||||
goto err_fclose;
|
||||
}
|
||||
|
||||
/** \brief Save a buffer to a file
|
||||
* \param buf Buffer to be written out
|
||||
* \param filename Path to the file
|
||||
* \return either JZ_SUCCESS, or one of the following errors
|
||||
* \retval JZ_ERR_OPEN_FILE file cannot be opened
|
||||
* \retval JZ_ERR_FILE_IO problem writing file data
|
||||
*/
|
||||
int jz_buffer_save(jz_buffer* buf, const char* filename)
|
||||
{
|
||||
int rc;
|
||||
FILE* f;
|
||||
|
||||
f = fopen(filename, "wb");
|
||||
if(!f)
|
||||
return JZ_ERR_OPEN_FILE;
|
||||
|
||||
if(fwrite(buf->data, buf->size, 1, f) != 1) {
|
||||
rc = JZ_ERR_FILE_IO;
|
||||
goto err_fclose;
|
||||
}
|
||||
|
||||
rc = JZ_SUCCESS;
|
||||
|
||||
err_fclose:
|
||||
fclose(f);
|
||||
return rc;
|
||||
}
|
167
rbutil/jztool/src/context.c
Normal file
167
rbutil/jztool/src/context.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool_private.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
/** \brief Allocate a library context
|
||||
* \returns New context or NULL if out of memory
|
||||
*/
|
||||
jz_context* jz_context_create(void)
|
||||
{
|
||||
jz_context* jz = malloc(sizeof(struct jz_context));
|
||||
if(!jz)
|
||||
return NULL;
|
||||
|
||||
memset(jz, 0, sizeof(struct jz_context));
|
||||
jz->log_level = JZ_LOG_ERROR;
|
||||
return jz;
|
||||
}
|
||||
|
||||
/** \brief Destroy the context and free its memory */
|
||||
void jz_context_destroy(jz_context* jz)
|
||||
{
|
||||
if(jz->usb_ctx) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "BUG: USB was not cleaned up properly");
|
||||
libusb_exit(jz->usb_ctx);
|
||||
}
|
||||
|
||||
free(jz);
|
||||
}
|
||||
|
||||
/** \brief Set a user data pointer. Useful for callbacks. */
|
||||
void jz_context_set_user_data(jz_context* jz, void* ptr)
|
||||
{
|
||||
jz->user_data = ptr;
|
||||
}
|
||||
|
||||
/** \brief Get the user data pointer */
|
||||
void* jz_context_get_user_data(jz_context* jz)
|
||||
{
|
||||
return jz->user_data;
|
||||
}
|
||||
|
||||
/** \brief Set the log message callback.
|
||||
* \note By default, no message callback is set! No messages will be logged
|
||||
* in this case, so ensure you set a callback if messages are desired.
|
||||
*/
|
||||
void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb)
|
||||
{
|
||||
jz->log_cb = cb;
|
||||
}
|
||||
|
||||
/** \brief Set the log level.
|
||||
*
|
||||
* Messages of less importance than the set log level are not logged.
|
||||
* The default log level is `JZ_LOG_WARNING`. The special log level
|
||||
* `JZ_LOG_IGNORE` can be used to disable all logging temporarily.
|
||||
*
|
||||
* The `JZ_LOG_DEBUG` log level is extremely verbose and will log all calls,
|
||||
* normally it's only useful for catching bugs.
|
||||
*/
|
||||
void jz_context_set_log_level(jz_context* jz, jz_log_level lev)
|
||||
{
|
||||
jz->log_level = lev;
|
||||
}
|
||||
|
||||
/** \brief Log an informational message.
|
||||
* \param lev Log level for this message
|
||||
* \param fmt `printf` style message format string
|
||||
*/
|
||||
void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...)
|
||||
{
|
||||
if(!jz->log_cb)
|
||||
return;
|
||||
if(lev == JZ_LOG_IGNORE)
|
||||
return;
|
||||
if(lev > jz->log_level)
|
||||
return;
|
||||
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(NULL, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if(n < 0)
|
||||
return;
|
||||
|
||||
char* buf = malloc(n + 1);
|
||||
if(!buf)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
n = vsnprintf(buf, n + 1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if(n >= 0)
|
||||
jz->log_cb(lev, buf);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/** \brief Log callback which writes messages to `stderr`.
|
||||
*/
|
||||
void jz_log_cb_stderr(jz_log_level lev, const char* msg)
|
||||
{
|
||||
static const char* const tags[] =
|
||||
{"ERROR", "WARNING", "NOTICE", "DETAIL", "DEBUG"};
|
||||
fprintf(stderr, "[%7s] %s\n", tags[lev], msg);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
/** \brief Sleep for `ms` milliseconds.
|
||||
*/
|
||||
void jz_sleepms(int ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
long ns = ms % 1000;
|
||||
ts.tv_nsec = ns * 1000 * 1000;
|
||||
ts.tv_sec = ms / 1000;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
int jz_context_ref_libusb(jz_context* jz)
|
||||
{
|
||||
if(jz->usb_ctxref == 0) {
|
||||
int rc = libusb_init(&jz->usb_ctx);
|
||||
if(rc < 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "libusb_init: %s", libusb_strerror(rc));
|
||||
return JZ_ERR_USB;
|
||||
}
|
||||
}
|
||||
|
||||
jz->usb_ctxref += 1;
|
||||
return JZ_SUCCESS;
|
||||
}
|
||||
|
||||
void jz_context_unref_libusb(jz_context* jz)
|
||||
{
|
||||
if(--jz->usb_ctxref == 0) {
|
||||
libusb_exit(jz->usb_ctx);
|
||||
jz->usb_ctx = NULL;
|
||||
}
|
||||
}
|
98
rbutil/jztool/src/device_info.c
Normal file
98
rbutil/jztool/src/device_info.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool.h"
|
||||
#include <string.h>
|
||||
|
||||
static const 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",
|
||||
.description = "FiiO M3K",
|
||||
.device_type = JZ_DEVICE_FIIOM3K,
|
||||
.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,
|
||||
},
|
||||
};
|
||||
|
||||
static const int infotable_size = sizeof(infotable)/sizeof(struct jz_device_info);
|
||||
|
||||
/** \brief Get the number of entries in the device info list */
|
||||
int jz_get_num_device_info(void)
|
||||
{
|
||||
return infotable_size;
|
||||
}
|
||||
|
||||
const jz_device_info* jz_get_device_info(jz_device_type type)
|
||||
{
|
||||
for(int i = 0; i < infotable_size; ++i)
|
||||
if(infotable[i].device_type == type)
|
||||
return &infotable[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** \brief Lookup info for a device by name, returns NULL if not found. */
|
||||
const jz_device_info* jz_get_device_info_named(const char* name)
|
||||
{
|
||||
for(int i = 0; i < infotable_size; ++i)
|
||||
if(!strcmp(infotable[i].name, name))
|
||||
return &infotable[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** \brief Get a device info entry by index, returns NULL if out of range. */
|
||||
const jz_device_info* jz_get_device_info_indexed(int index)
|
||||
{
|
||||
if(index < infotable_size)
|
||||
return &infotable[index];
|
||||
else
|
||||
return NULL;
|
||||
}
|
283
rbutil/jztool/src/fiiom3k.c
Normal file
283
rbutil/jztool/src/fiiom3k.c
Normal file
|
@ -0,0 +1,283 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool.h"
|
||||
#include <string.h>
|
||||
|
||||
#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)
|
||||
{
|
||||
jz_buffer* buf = jz_buffer_alloc(IMAGE_SIZE, NULL);
|
||||
if(!buf)
|
||||
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);
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl)
|
||||
{
|
||||
struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE;
|
||||
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 = fiiom3k_action_setup(jz, pl, &state);
|
||||
if(rc < 0)
|
||||
goto error;
|
||||
|
||||
rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]);
|
||||
if(rc < 0)
|
||||
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)
|
||||
goto error;
|
||||
|
||||
rc = fiiom3k_action_setup(jz, pl, &state);
|
||||
if(rc < 0)
|
||||
goto error;
|
||||
|
||||
jz_buffer* img_buf = state.bufs[IMGBUF];
|
||||
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;
|
||||
}
|
179
rbutil/jztool/src/identify_file.c
Normal file
179
rbutil/jztool/src/identify_file.c
Normal file
|
@ -0,0 +1,179 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Following is copied from mkspl-x1000, basically */
|
||||
struct x1000_spl_header {
|
||||
uint8_t magic[8];
|
||||
uint8_t type;
|
||||
uint8_t crc7;
|
||||
uint8_t ppb;
|
||||
uint8_t bpp;
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
static const uint8_t x1000_spl_header_magic[8] =
|
||||
{0x06, 0x05, 0x04, 0x03, 0x02, 0x55, 0xaa, 0x55};
|
||||
|
||||
static const size_t X1000_SPL_HEADER_SIZE = 2 * 1024;
|
||||
|
||||
static uint8_t crc7(const uint8_t* buf, size_t len)
|
||||
{
|
||||
/* table-based computation of CRC7 */
|
||||
static const uint8_t t[256] = {
|
||||
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f,
|
||||
0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
|
||||
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26,
|
||||
0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
|
||||
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d,
|
||||
0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
|
||||
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14,
|
||||
0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
|
||||
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b,
|
||||
0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
|
||||
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42,
|
||||
0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
|
||||
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69,
|
||||
0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
|
||||
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70,
|
||||
0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
|
||||
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e,
|
||||
0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
|
||||
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67,
|
||||
0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
|
||||
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
|
||||
0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
|
||||
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55,
|
||||
0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
|
||||
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a,
|
||||
0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
|
||||
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03,
|
||||
0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
|
||||
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28,
|
||||
0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
|
||||
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31,
|
||||
0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
|
||||
};
|
||||
|
||||
uint8_t crc = 0;
|
||||
while(len--)
|
||||
crc = t[(crc << 1) ^ *buf++];
|
||||
return crc;
|
||||
}
|
||||
|
||||
int jz_identify_x1000_spl(const void* data, size_t len)
|
||||
{
|
||||
/* Use <= check because a header-only file is not really valid,
|
||||
* it should have at least one byte in it... */
|
||||
if(len <= X1000_SPL_HEADER_SIZE)
|
||||
return JZ_IDERR_WRONG_SIZE;
|
||||
|
||||
/* Look for header magic bytes */
|
||||
const struct x1000_spl_header* header = (const struct x1000_spl_header*)data;
|
||||
if(memcmp(header->magic, x1000_spl_header_magic, 8))
|
||||
return JZ_IDERR_BAD_HEADER;
|
||||
|
||||
/* Length stored in the header should equal the length of the file */
|
||||
if(header->length != len)
|
||||
return JZ_IDERR_WRONG_SIZE;
|
||||
|
||||
/* Compute the CRC7 checksum; it only covers the SPL code */
|
||||
const uint8_t* dat = (const uint8_t*)data;
|
||||
uint8_t sum = crc7(&dat[X1000_SPL_HEADER_SIZE], len - X1000_SPL_HEADER_SIZE);
|
||||
if(header->crc7 != sum)
|
||||
return JZ_IDERR_BAD_CHECKSUM;
|
||||
|
||||
return JZ_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
static const struct scramble_model_info {
|
||||
const char* name;
|
||||
int model_num;
|
||||
} scramble_models[] = {
|
||||
{"fiio", 114},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
int jz_identify_scramble_image(const void* data, size_t len)
|
||||
{
|
||||
/* 4 bytes checksum + 4 bytes player model */
|
||||
if(len < 8)
|
||||
return JZ_IDERR_WRONG_SIZE;
|
||||
|
||||
/* Look up the model number */
|
||||
const uint8_t* dat = (const uint8_t*)data;
|
||||
const struct scramble_model_info* model_info = &scramble_models[0];
|
||||
for(; model_info->name != NULL; ++model_info)
|
||||
if(!memcmp(&dat[4], model_info->name, 4))
|
||||
break;
|
||||
|
||||
if(model_info->name == NULL)
|
||||
return JZ_IDERR_UNRECOGNIZED_MODEL;
|
||||
|
||||
/* Compute the checksum */
|
||||
uint32_t sum = model_info->model_num;
|
||||
for(size_t i = 8; i < len; ++i)
|
||||
sum += dat[i];
|
||||
|
||||
/* Compare with file's checksum, it's stored in big-endian form */
|
||||
uint32_t fsum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
|
||||
if(sum != fsum)
|
||||
return JZ_IDERR_BAD_CHECKSUM;
|
||||
|
||||
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;
|
||||
}
|
44
rbutil/jztool/src/jztool_private.h
Normal file
44
rbutil/jztool/src/jztool_private.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef JZTOOL_PRIVATE_H
|
||||
#define JZTOOL_PRIVATE_H
|
||||
|
||||
#include "jztool.h"
|
||||
#include <libusb.h>
|
||||
|
||||
struct jz_context {
|
||||
void* user_data;
|
||||
jz_log_cb log_cb;
|
||||
jz_log_level log_level;
|
||||
libusb_context* usb_ctx;
|
||||
int usb_ctxref;
|
||||
};
|
||||
|
||||
struct jz_usbdev {
|
||||
jz_context* jz;
|
||||
libusb_device_handle* handle;
|
||||
};
|
||||
|
||||
int jz_context_ref_libusb(jz_context* jz);
|
||||
void jz_context_unref_libusb(jz_context* jz);
|
||||
|
||||
#endif /* JZTOOL_PRIVATE_H */
|
135
rbutil/jztool/src/paramlist.c
Normal file
135
rbutil/jztool/src/paramlist.c
Normal file
|
@ -0,0 +1,135 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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;
|
||||
}
|
203
rbutil/jztool/src/usb.c
Normal file
203
rbutil/jztool/src/usb.c
Normal file
|
@ -0,0 +1,203 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "jztool_private.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define VR_GET_CPU_INFO 0
|
||||
#define VR_SET_DATA_ADDRESS 1
|
||||
#define VR_SET_DATA_LENGTH 2
|
||||
#define VR_FLUSH_CACHES 3
|
||||
#define VR_PROGRAM_START1 4
|
||||
#define VR_PROGRAM_START2 5
|
||||
|
||||
int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id)
|
||||
{
|
||||
int rc;
|
||||
jz_usbdev* dev = NULL;
|
||||
libusb_device_handle* usb_handle = NULL;
|
||||
libusb_device** dev_list = NULL;
|
||||
ssize_t dev_index = -1, dev_count;
|
||||
|
||||
rc = jz_context_ref_libusb(jz);
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
|
||||
dev = malloc(sizeof(struct jz_usbdev));
|
||||
if(!dev) {
|
||||
rc = JZ_ERR_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list);
|
||||
if(dev_count < 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count));
|
||||
rc = JZ_ERR_USB;
|
||||
goto error;
|
||||
}
|
||||
|
||||
for(ssize_t i = 0; i < dev_count; ++i) {
|
||||
struct libusb_device_descriptor desc;
|
||||
rc = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||
if(rc < 0) {
|
||||
jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s",
|
||||
libusb_strerror(rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(desc.idVendor != vend_id || desc.idProduct != prod_id)
|
||||
continue;
|
||||
|
||||
if(dev_index >= 0) {
|
||||
/* not the best, but it is the safest thing */
|
||||
jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x",
|
||||
(unsigned int)vend_id, (unsigned int)prod_id);
|
||||
jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again");
|
||||
rc = JZ_ERR_NO_DEVICE;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_index = i;
|
||||
}
|
||||
|
||||
if(dev_index < 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%05x found",
|
||||
(unsigned int)vend_id, (unsigned int)prod_id);
|
||||
rc = JZ_ERR_NO_DEVICE;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = libusb_open(dev_list[dev_index], &usb_handle);
|
||||
if(rc < 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc));
|
||||
rc = JZ_ERR_USB;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = libusb_claim_interface(usb_handle, 0);
|
||||
if(rc < 0) {
|
||||
jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc));
|
||||
rc = JZ_ERR_USB;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev->jz = jz;
|
||||
dev->handle = usb_handle;
|
||||
*devptr = dev;
|
||||
rc = JZ_SUCCESS;
|
||||
|
||||
exit:
|
||||
if(dev_list)
|
||||
libusb_free_device_list(dev_list, true);
|
||||
return rc;
|
||||
|
||||
error:
|
||||
if(dev)
|
||||
free(dev);
|
||||
if(usb_handle)
|
||||
libusb_close(usb_handle);
|
||||
jz_context_unref_libusb(jz);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
void jz_usb_close(jz_usbdev* dev)
|
||||
{
|
||||
libusb_release_interface(dev->handle, 0);
|
||||
libusb_close(dev->handle);
|
||||
jz_context_unref_libusb(dev->jz);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg)
|
||||
{
|
||||
int rc = libusb_control_transfer(dev->handle,
|
||||
LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
|
||||
req, arg >> 16, arg & 0xffff, NULL, 0, 1000);
|
||||
|
||||
if(rc < 0) {
|
||||
jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc));
|
||||
rc = JZ_ERR_USB;
|
||||
} else {
|
||||
rc = JZ_SUCCESS;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf)
|
||||
{
|
||||
int xfered = 0;
|
||||
int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1;
|
||||
int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000);
|
||||
|
||||
if(rc < 0) {
|
||||
jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc));
|
||||
rc = JZ_ERR_USB;
|
||||
} else if(xfered != (int)len) {
|
||||
jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered");
|
||||
rc = JZ_ERR_USB;
|
||||
} else {
|
||||
rc = JZ_SUCCESS;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr,
|
||||
size_t len, void* data)
|
||||
{
|
||||
int rc;
|
||||
rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr);
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len);
|
||||
if(rc < 0)
|
||||
return rc;
|
||||
|
||||
return jz_usb_transfer(dev, write, len, data);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data)
|
||||
{
|
||||
return jz_usb_sendrecv(dev, false, addr, len, data);
|
||||
}
|
||||
|
||||
int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
|
||||
{
|
||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr);
|
||||
}
|
||||
|
||||
int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
|
||||
{
|
||||
return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr);
|
||||
}
|
||||
|
||||
int jz_usb_flush_caches(jz_usbdev* dev)
|
||||
{
|
||||
return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0);
|
||||
}
|
193
rbutil/jztool/src/x1000.c
Normal file
193
rbutil/jztool/src/x1000.c
Normal file
|
@ -0,0 +1,193 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <endian.h> // TODO: portability
|
||||
#include <string.h>
|
||||
|
||||
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 = htole32(args->command);
|
||||
args->param1 = htole32(args->param1);
|
||||
args->param2 = htole32(args->param2);
|
||||
args->flags = htole32(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 = le32toh(status->err_code);
|
||||
status->reserved = le32toh(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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue