summaryrefslogtreecommitdiff
path: root/utils/jztool
diff options
context:
space:
mode:
Diffstat (limited to 'utils/jztool')
-rw-r--r--utils/jztool/Makefile47
-rw-r--r--utils/jztool/README.md135
-rw-r--r--utils/jztool/include/jztool.h202
-rw-r--r--utils/jztool/jztool.c212
-rw-r--r--utils/jztool/src/buffer.c134
-rw-r--r--utils/jztool/src/context.c177
-rw-r--r--utils/jztool/src/device_info.c109
-rw-r--r--utils/jztool/src/identify_file.c170
-rw-r--r--utils/jztool/src/jztool_private.h44
-rw-r--r--utils/jztool/src/ucl_unpack.c128
-rw-r--r--utils/jztool/src/usb.c291
-rw-r--r--utils/jztool/src/x1000.c180
12 files changed, 1829 insertions, 0 deletions
diff --git a/utils/jztool/Makefile b/utils/jztool/Makefile
new file mode 100644
index 0000000000..d1bbae578c
--- /dev/null
+++ b/utils/jztool/Makefile
@@ -0,0 +1,47 @@
1# __________ __ ___.
2# Open \______ \ ____ ____ | | _\_ |__ _______ ___
3# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
6# \/ \/ \/ \/ \/
7
8CFLAGS += -Wall -Wextra -Iinclude -I../../tools/ucl/include -I../../lib/microtar/src
9OUTPUT = jztool
10
11ifdef RELEASE
12CFLAGS += -Os -DNDEBUG
13else
14CFLAGS += -O0 -ggdb
15endif
16
17LIBSOURCES := src/buffer.c src/context.c src/device_info.c \
18 src/identify_file.c src/ucl_unpack.c src/usb.c src/x1000.c
19SOURCES := $(LIBSOURCES) jztool.c
20EXTRADEPS := libucl.a libmicrotar.a
21
22CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -)
23
24ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32)
25# TODO: support Windows
26else
27ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE)
28# Mac, tested on x86 only -- may need to adjust paths if building on ARM.
29# paths should work with homebrew libusb.
30LIBUSB_CFLAGS ?= -I/usr/local/include/libusb-1.0
31ifdef STATIC
32LIBUSB_LDOPTS ?= /usr/local/lib/libusb-1.0.a -framework IOKit -framework CoreFoundation
33else
34LIBUSB_LDOPTS ?= -L/usr/local/lib -lusb-1.0
35endif
36else
37# Linux; note for static builds you need to build a copy of libusb without
38# udev support and specify the includes / libs manually
39LIBUSB_CFLAGS ?= `pkg-config --cflags libusb-1.0`
40LIBUSB_LDOPTS ?= `pkg-config --libs libusb-1.0`
41endif
42endif
43
44CFLAGS += $(LIBUSB_CFLAGS)
45LDOPTS += $(LIBUSB_LDOPTS)
46
47include ../libtools.make
diff --git a/utils/jztool/README.md b/utils/jztool/README.md
new file mode 100644
index 0000000000..72c630c6c3
--- /dev/null
+++ b/utils/jztool/README.md
@@ -0,0 +1,135 @@
1# jztool -- Ingenic device utility & bootloader installer
2
3The `jztool` utility can help install, backup, and restore the bootloader on
4Rockbox players based on a supported Ingenic SoC (currently only the X1000).
5
6## Running jztool
7
8### Getting a bootloader
9
10To use `jztool` you need to compile or download a bootloader for your player.
11It's recommended to use only official released bootloaders, since bootloaders
12compiled from Git are not tested and might be buggy.
13
14You can download released bootloaders from <https://download.rockbox.org/>.
15
16The bootloader file is named after the target: for example, the FiiO M3K
17bootloader is called `bootloader.m3k`. The FiiO M3K is used as an example
18here, but the instructions apply to all X1000-based players.
19
20Use `jztool --help` to find out the model name of your player.
21
22### Entering USB boot mode
23
24USB boot mode is a low-level mode provided by the CPU which allows a computer
25to load firmware onto the device. You need to put your player into this mode
26manually before using `jztool` (unfortunately, it can't be done automatically.)
27
28To connect the player in USB boot mode, follow these steps:
29
301. Ensure the player is fully powered off.
312. Plug one end of the USB cable into your player.
323. Hold down your player's USB boot key (see below).
334. Plug the other end of the USB cable into your computer.
345. Let go of the USB boot key.
35
36The USB boot key depends on your player:
37
38- FiiO M3K: Volume Down
39- Shanling Q1: Play
40- Eros Q: Menu
41
42### Linux/Mac
43
44Run the following command in a terminal. Note that on Linux, you will need to
45have root access to allow libusb to access the USB device.
46
47```sh
48# Linux / Mac
49# NOTE: root permissions are required on Linux to access the USB device
50# eg. with 'sudo' or 'su -c' depending on your distro.
51$ ./jztool fiiom3k load bootloader.m3k
52```
53
54### Windows
55
56To allow `jztool` access to your player in USB boot mode, you need to install
57the WinUSB driver. The recommended way to install it is using Zadig, which
58may be downloaded from its homepage <https://zadig.akeo.ie>. Please note
59this is 3rd party software not maintained or supported by Rockbox developers.
60(Zadig will require administrator access on the machine you are using.)
61
62When running Zadig you must select the WinUSB driver; the other driver options
63will not work properly with `jztool`. You will have to select the correct USB
64device in Zadig. All X1000-based players use the same USB ID while in USB boot
65mode, listed below. NOTE: the device name may show only as "X" and a hollow
66square in Zadig. The IDs will not change, so those are the most reliable way
67to confirm you have selected the correct device.
68
69```
70Name: Ingenic Semiconductor Co.,Ltd X1000
71USB ID: A108 1000
72```
73
74Assuming you installed the WinUSB driver successfully, open a command prompt
75in the folder containing `jztool`. Administrator access is not required for
76this step.
77
78Type the following command to load the Rockbox bootloader:
79
80```sh
81# Windows
82$ jztool.exe fiiom3k load bootloader.m3k
83```
84
85## Using the recovery menu
86
87If `jztool` runs successfully your player will display the Rockbox bootloader's
88recovery menu. If you want to permanently install Rockbox to your device, copy
89the bootloader file you downloaded to the root of your SD card, insert the SD
90card to your player, and choose "Install/update bootloader" from the menu.
91
92It is _highly_ recommended that you take a backup of your existing bootloader
93in case of any trouble -- choose "Backup bootloader" from the recovery menu.
94The backup file is called `PLAYER-boot.bin`, where `PLAYER` is the model name.
95(Example: `fiiom3k-boot.bin`.)
96
97You can restore the backup later by putting it on the root of your SD card and
98selecting "Restor bootloader" in the recovery menu.
99
100After installing the Rockbox bootloader, you can access the recovery menu by
101holding a key while booting:
102
103- FiiO M3K: Volume Up
104- Shanling Q1: Next (button on the lower left)
105- Eros Q: Volume Up
106
107### Known issues
108
109- When using the bootloader's USB mode, you may get stuck on "Waiting for USB"
110 even though the cable is already plugged in. If this occurs, unplug the USB
111 cable and plug it back in to trigger the connection.
112
113
114## TODO list
115
116### Add better documentation and logging
117
118There's only a bare minimum of documentation, and logging is sparse, not
119really enough to debug problems.
120
121Some of the error messages could be friendlier too.
122
123### Integration with the Rockbox utility
124
125Adding support to the Rockbox utility should be mostly boilerplate since the
126jztool library wraps all the troublesome details.
127
128Permissions are an issue on Linux because by default only root can access
129"raw" USB devices. If we want to package rbutil for distro we can install
130a udev rule to allow access to the specific USB IDs we need, eg. allowing
131users in the "wheel" group to access the device.
132
133On Windows and Mac, no special permissions are needed to access USB devices
134assuming the drivers are set up. (Zadig does require administrator access
135to run, but that's external to the Rockbox utility.)
diff --git a/utils/jztool/include/jztool.h b/utils/jztool/include/jztool.h
new file mode 100644
index 0000000000..9d3c08c5bc
--- /dev/null
+++ b/utils/jztool/include/jztool.h
@@ -0,0 +1,202 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef JZTOOL_H
23#define JZTOOL_H
24
25#include <stdint.h>
26#include <stddef.h>
27
28#ifdef __cplusplus
29extern "C" {
30#endif
31
32/******************************************************************************
33 * Types, enumerations, etc
34 */
35
36#define JZ_CPUINFO_BUFLEN 9
37
38typedef struct jz_context jz_context;
39typedef struct jz_usbdev jz_usbdev;
40typedef struct jz_device_info jz_device_info;
41typedef struct jz_cpu_info jz_cpu_info;
42typedef struct jz_buffer jz_buffer;
43
44typedef enum jz_error jz_error;
45typedef enum jz_identify_error jz_identify_error;
46typedef enum jz_log_level jz_log_level;
47typedef enum jz_device_type jz_device_type;
48typedef enum jz_cpu_type jz_cpu_type;
49
50typedef void(*jz_log_cb)(jz_log_level, const char*);
51
52enum jz_error {
53 JZ_SUCCESS = 0,
54 JZ_ERR_OUT_OF_MEMORY = -1,
55 JZ_ERR_OPEN_FILE = -2,
56 JZ_ERR_FILE_IO = -3,
57 JZ_ERR_USB = -4,
58 JZ_ERR_NO_DEVICE = -5,
59 JZ_ERR_BAD_FILE_FORMAT = -6,
60 JZ_ERR_FLASH_ERROR = -7,
61 JZ_ERR_OTHER = -99,
62};
63
64enum jz_identify_error {
65 JZ_IDERR_OTHER = -1,
66 JZ_IDERR_WRONG_SIZE = -2,
67 JZ_IDERR_BAD_HEADER = -3,
68 JZ_IDERR_BAD_CHECKSUM = -4,
69 JZ_IDERR_UNRECOGNIZED_MODEL = -5,
70};
71
72enum jz_log_level {
73 JZ_LOG_IGNORE = -1,
74 JZ_LOG_ERROR = 0,
75 JZ_LOG_WARNING = 1,
76 JZ_LOG_NOTICE = 2,
77 JZ_LOG_DETAIL = 3,
78 JZ_LOG_DEBUG = 4,
79};
80
81enum jz_device_type {
82 JZ_DEVICE_FIIOM3K,
83 JZ_DEVICE_SHANLINGQ1,
84 JZ_DEVICE_EROSQ,
85 JZ_NUM_DEVICES,
86};
87
88enum jz_cpu_type {
89 JZ_CPU_X1000,
90 JZ_NUM_CPUS,
91};
92
93struct jz_device_info {
94 /* internal device name and file extension */
95 const char* name;
96 const char* file_ext;
97
98 /* human-readable name */
99 const char* description;
100
101 /* device and CPU type */
102 jz_device_type device_type;
103 jz_cpu_type cpu_type;
104
105 /* USB IDs of the device in mass storage mode */
106 uint16_t vendor_id;
107 uint16_t product_id;
108};
109
110struct jz_cpu_info {
111 /* CPU info string, as reported by the boot ROM */
112 const char* info_str;
113
114 /* USB IDs of the boot ROM */
115 uint16_t vendor_id;
116 uint16_t product_id;
117
118 /* default addresses for running binaries */
119 uint32_t stage1_load_addr;
120 uint32_t stage1_exec_addr;
121 uint32_t stage2_load_addr;
122 uint32_t stage2_exec_addr;
123};
124
125struct jz_buffer {
126 size_t size;
127 uint8_t* data;
128};
129
130/******************************************************************************
131 * Library context and general functions
132 */
133
134jz_context* jz_context_create(void);
135void jz_context_destroy(jz_context* jz);
136
137void jz_context_set_user_data(jz_context* jz, void* ptr);
138void* jz_context_get_user_data(jz_context* jz);
139
140void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb);
141void jz_context_set_log_level(jz_context* jz, jz_log_level lev);
142
143void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...);
144void jz_log_cb_stderr(jz_log_level lev, const char* msg);
145
146void jz_sleepms(int ms);
147
148/******************************************************************************
149 * Device and file info
150 */
151
152const jz_device_info* jz_get_device_info(jz_device_type type);
153const jz_device_info* jz_get_device_info_named(const char* name);
154const jz_device_info* jz_get_device_info_indexed(int index);
155
156const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type);
157const jz_cpu_info* jz_get_cpu_info_named(const char* info_str);
158
159int jz_identify_x1000_spl(const void* data, size_t len);
160int jz_identify_scramble_image(const void* data, size_t len);
161
162/******************************************************************************
163 * USB boot ROM protocol
164 */
165
166int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id);
167void jz_usb_close(jz_usbdev* dev);
168
169int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data);
170int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data);
171int jz_usb_start1(jz_usbdev* dev, uint32_t addr);
172int jz_usb_start2(jz_usbdev* dev, uint32_t addr);
173int jz_usb_flush_caches(jz_usbdev* dev);
174int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen);
175
176/******************************************************************************
177 * Rockbox loader (all functions are model-specific, see docs)
178 */
179
180int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename);
181
182/******************************************************************************
183 * Buffer API and other functions
184 */
185
186jz_buffer* jz_buffer_alloc(size_t size, const void* data);
187void jz_buffer_free(jz_buffer* buf);
188
189int jz_buffer_load(jz_buffer** buf, const char* filename);
190int jz_buffer_save(jz_buffer* buf, const char* filename);
191
192jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len);
193
194/******************************************************************************
195 * END
196 */
197
198#ifdef __cplusplus
199}
200#endif
201
202#endif /* JZTOOL_H */
diff --git a/utils/jztool/jztool.c b/utils/jztool/jztool.c
new file mode 100644
index 0000000000..dcd78137b3
--- /dev/null
+++ b/utils/jztool/jztool.c
@@ -0,0 +1,212 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <stdbool.h>
27
28jz_context* jz = NULL;
29jz_usbdev* usbdev = NULL;
30const jz_device_info* dev_info = NULL;
31const jz_cpu_info* cpu_info = NULL;
32
33void usage_x1000(void)
34{
35 printf(
36"Usage:\n"
37" jztool fiiom3k load <bootloader.m3k>\n"
38" jztool shanlingq1 load <bootloader.q1>\n"
39" jztool erosq load <bootloader.erosq>\n"
40"\n"
41"The 'load' command is used to boot the Rockbox bootloader in recovery\n"
42"mode, which allows you to install the Rockbox bootloader and backup or\n"
43"restore bootloader images. You need to connect your player in USB boot\n"
44"mode in order to use this tool.\n"
45"\n"
46"To connect the player in USB boot mode, follow these steps:\n"
47"\n"
48"1. Ensure the player is fully powered off.\n"
49"2. Plug one end of the USB cable into your player.\n"
50"3. Hold down your player's USB boot key (see below).\n"
51"4. Plug the other end of the USB cable into your computer.\n"
52"5. Let go of the USB boot key.\n"
53"\n"
54"USB boot keys:\n"
55"\n"
56" FiiO M3K - Volume Down\n"
57" Shanling Q1 - Play\n"
58" Eros Q - Menu\n"
59"\n"
60"Not all players give a visible indication that they are in USB boot mode.\n"
61"If you're having trouble connecting your player, try resetting it by\n"
62"holding the power button for 10 seconds, and try the above steps again.\n"
63"\n"
64"Note for Windows users: you need to install the WinUSB driver using a\n"
65"3rd-party tool such as Zadig <https://zadig.akeo.ie> before this tool\n"
66"can access your player in USB boot mode. You need to run Zadig while the\n"
67"player is plugged in and in USB boot mode. For more details check the\n"
68"jztool README.md file or visit <https://rockbox.org/wiki/IngenicX1000>.\n"
69"\n");
70
71 exit(4);
72}
73
74int cmdline_x1000(int argc, char** argv)
75{
76 if(argc < 2 || strcmp(argv[0], "load")) {
77 usage_x1000();
78 return 2;
79 }
80
81 int rc = jz_usb_open(jz, &usbdev, cpu_info->vendor_id, cpu_info->product_id);
82 if(rc < 0) {
83 jz_log(jz, JZ_LOG_ERROR, "Cannot open USB device: %d", rc);
84 return 1;
85 }
86
87 rc = jz_x1000_boot(usbdev, dev_info->device_type, argv[1]);
88 if(rc < 0) {
89 jz_log(jz, JZ_LOG_ERROR, "Boot failed: %d", rc);
90 return 1;
91 }
92
93 return 0;
94}
95
96void usage(void)
97{
98 printf("Usage:\n"
99 " jztool [global options] <device> <command> [command arguments]\n"
100 "\n"
101 "Global options:\n"
102 " -h, --help Display this help\n"
103 " -q, --quiet Don't log anything except errors\n"
104 " -v, --verbose Display detailed logging output\n\n");
105
106 printf("Supported devices:\n\n");
107 for(int i = 0; i < JZ_NUM_DEVICES; ++i) {
108 const jz_device_info* info = jz_get_device_info_indexed(i);
109 printf(" %s - %s\n", info->name, info->description);
110 }
111
112 printf("\n"
113 "For device-specific help run 'jztool DEVICE' without arguments,\n"
114 "eg. 'jztool fiiom3k' will display help for the FiiO M3K.\n");
115
116 exit(4);
117}
118
119void cleanup(void)
120{
121 if(usbdev)
122 jz_usb_close(usbdev);
123 if(jz)
124 jz_context_destroy(jz);
125}
126
127int main(int argc, char** argv)
128{
129 if(argc < 2)
130 usage();
131
132 /* Library initialization */
133 jz = jz_context_create();
134 if(!jz) {
135 fprintf(stderr, "ERROR: Can't create context");
136 return 1;
137 }
138
139 atexit(cleanup);
140 jz_context_set_log_cb(jz, jz_log_cb_stderr);
141 jz_context_set_log_level(jz, JZ_LOG_NOTICE);
142
143 /* Parse global options */
144 --argc, ++argv;
145 while(argc > 0 && argv[0][0] == '-') {
146 if(!strcmp(*argv, "-h") || !strcmp(*argv, "--help"))
147 usage();
148 else if(!strcmp(*argv, "-q") || !strcmp(*argv, "--quiet"))
149 jz_context_set_log_level(jz, JZ_LOG_ERROR);
150 else if(!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose"))
151 jz_context_set_log_level(jz, JZ_LOG_DETAIL);
152 else if(!strcmp(*argv, "-l") || !strcmp(*argv, "--loglevel")) {
153 ++argv;
154 if(--argc == 0) {
155 jz_log(jz, JZ_LOG_ERROR, "Missing argument to option %s", *argv);
156 exit(2);
157 }
158
159 enum jz_log_level level;
160 if(!strcmp(*argv, "ignore"))
161 level = JZ_LOG_IGNORE;
162 else if(!strcmp(*argv, "error"))
163 level = JZ_LOG_ERROR;
164 else if(!strcmp(*argv, "warning"))
165 level = JZ_LOG_WARNING;
166 else if(!strcmp(*argv, "notice"))
167 level = JZ_LOG_NOTICE;
168 else if(!strcmp(*argv, "detail"))
169 level = JZ_LOG_DETAIL;
170 else if(!strcmp(*argv, "debug"))
171 level = JZ_LOG_DEBUG;
172 else {
173 jz_log(jz, JZ_LOG_ERROR, "Invalid log level '%s'", *argv);
174 exit(2);
175 }
176
177 jz_context_set_log_level(jz, level);
178 } else {
179 jz_log(jz, JZ_LOG_ERROR, "Invalid global option '%s'", *argv);
180 exit(2);
181 }
182
183 --argc, ++argv;
184 }
185
186 /* Read the device type */
187 if(argc == 0) {
188 jz_log(jz, JZ_LOG_ERROR, "No device specified (try jztool --help)");
189 exit(2);
190 }
191
192 dev_info = jz_get_device_info_named(*argv);
193 if(!dev_info) {
194 jz_log(jz, JZ_LOG_ERROR, "Unknown device '%s' (try jztool --help)", *argv);
195 exit(2);
196 }
197
198 cpu_info = jz_get_cpu_info(dev_info->cpu_type);
199
200 /* Dispatch to device handler */
201 --argc, ++argv;
202 switch(dev_info->device_type) {
203 case JZ_DEVICE_FIIOM3K:
204 case JZ_DEVICE_SHANLINGQ1:
205 case JZ_DEVICE_EROSQ:
206 return cmdline_x1000(argc, argv);
207
208 default:
209 jz_log(jz, JZ_LOG_ERROR, "INTERNAL ERROR: unhandled device type");
210 return 1;
211 }
212}
diff --git a/utils/jztool/src/buffer.c b/utils/jztool/src/buffer.c
new file mode 100644
index 0000000000..9e9c9ff5d1
--- /dev/null
+++ b/utils/jztool/src/buffer.c
@@ -0,0 +1,134 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <stdlib.h>
24#include <string.h>
25#include <stdio.h>
26
27/** \brief Allocate a buffer, optionally providing its contents.
28 * \param size Number of bytes to allocate
29 * \param data Initial contents of the buffer, must be at least `size` bytes
30 * \return Pointer to buffer or NULL if out of memory.
31 * \note The buffer will not take ownership of the `data` pointer, instead it
32 * allocates a fresh buffer and copies the contents of `data` into it.
33 */
34jz_buffer* jz_buffer_alloc(size_t size, const void* data)
35{
36 jz_buffer* buf = malloc(sizeof(struct jz_buffer));
37 if(!buf)
38 return NULL;
39
40 buf->data = malloc(size);
41 if(!buf->data) {
42 free(buf);
43 return NULL;
44 }
45
46 if(data)
47 memcpy(buf->data, data, size);
48
49 buf->size = size;
50 return buf;
51}
52
53/** \brief Free a buffer
54 */
55void jz_buffer_free(jz_buffer* buf)
56{
57 if(buf) {
58 free(buf->data);
59 free(buf);
60 }
61}
62
63/** \brief Load a buffer from a file
64 * \param buf Returns loaded buffer on success, unmodified on error
65 * \param filename Path to the file
66 * \return either JZ_SUCCESS, or one of the following errors
67 * \retval JZ_ERR_OPEN_FILE file cannot be opened
68 * \retval JZ_ERR_OUT_OF_MEMORY cannot allocate buffer to hold file contents
69 * \retval JZ_ERR_FILE_IO problem reading file data
70 */
71int jz_buffer_load(jz_buffer** buf, const char* filename)
72{
73 FILE* f;
74 jz_buffer* b;
75 int rc;
76
77 f = fopen(filename, "rb");
78 if(!f)
79 return JZ_ERR_OPEN_FILE;
80
81 fseek(f, 0, SEEK_END);
82 int size = ftell(f);
83 fseek(f, 0, SEEK_SET);
84
85 b = jz_buffer_alloc(size, NULL);
86 if(!b) {
87 rc = JZ_ERR_OUT_OF_MEMORY;
88 goto err_fclose;
89 }
90
91 if(fread(b->data, size, 1, f) != 1) {
92 rc = JZ_ERR_FILE_IO;
93 goto err_free_buf;
94 }
95
96 rc = JZ_SUCCESS;
97 *buf = b;
98
99 err_fclose:
100 fclose(f);
101 return rc;
102
103 err_free_buf:
104 jz_buffer_free(b);
105 goto err_fclose;
106}
107
108/** \brief Save a buffer to a file
109 * \param buf Buffer to be written out
110 * \param filename Path to the file
111 * \return either JZ_SUCCESS, or one of the following errors
112 * \retval JZ_ERR_OPEN_FILE file cannot be opened
113 * \retval JZ_ERR_FILE_IO problem writing file data
114 */
115int jz_buffer_save(jz_buffer* buf, const char* filename)
116{
117 int rc;
118 FILE* f;
119
120 f = fopen(filename, "wb");
121 if(!f)
122 return JZ_ERR_OPEN_FILE;
123
124 if(fwrite(buf->data, buf->size, 1, f) != 1) {
125 rc = JZ_ERR_FILE_IO;
126 goto err_fclose;
127 }
128
129 rc = JZ_SUCCESS;
130
131 err_fclose:
132 fclose(f);
133 return rc;
134}
diff --git a/utils/jztool/src/context.c b/utils/jztool/src/context.c
new file mode 100644
index 0000000000..d269d1eece
--- /dev/null
+++ b/utils/jztool/src/context.c
@@ -0,0 +1,177 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool_private.h"
23#include <string.h>
24#include <stdlib.h>
25#include <stddef.h>
26#include <stdarg.h>
27#include <stdio.h>
28#include <time.h>
29
30#ifdef WIN32
31# include <windows.h>
32#endif
33
34/** \brief Allocate a library context
35 * \returns New context or NULL if out of memory
36 */
37jz_context* jz_context_create(void)
38{
39 jz_context* jz = malloc(sizeof(struct jz_context));
40 if(!jz)
41 return NULL;
42
43 memset(jz, 0, sizeof(struct jz_context));
44 jz->log_level = JZ_LOG_ERROR;
45 return jz;
46}
47
48/** \brief Destroy the context and free its memory */
49void jz_context_destroy(jz_context* jz)
50{
51 if(jz->usb_ctx) {
52 jz_log(jz, JZ_LOG_ERROR, "BUG: USB was not cleaned up properly");
53 libusb_exit(jz->usb_ctx);
54 }
55
56 free(jz);
57}
58
59/** \brief Set a user data pointer. Useful for callbacks. */
60void jz_context_set_user_data(jz_context* jz, void* ptr)
61{
62 jz->user_data = ptr;
63}
64
65/** \brief Get the user data pointer */
66void* jz_context_get_user_data(jz_context* jz)
67{
68 return jz->user_data;
69}
70
71/** \brief Set the log message callback.
72 * \note By default, no message callback is set! No messages will be logged
73 * in this case, so ensure you set a callback if messages are desired.
74 */
75void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb)
76{
77 jz->log_cb = cb;
78}
79
80/** \brief Set the log level.
81 *
82 * Messages of less importance than the set log level are not logged.
83 * The default log level is `JZ_LOG_WARNING`. The special log level
84 * `JZ_LOG_IGNORE` can be used to disable all logging temporarily.
85 *
86 * The `JZ_LOG_DEBUG` log level is extremely verbose and will log all calls,
87 * normally it's only useful for catching bugs.
88 */
89void jz_context_set_log_level(jz_context* jz, jz_log_level lev)
90{
91 jz->log_level = lev;
92}
93
94/** \brief Log an informational message.
95 * \param lev Log level for this message
96 * \param fmt `printf` style message format string
97 */
98void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...)
99{
100 if(!jz->log_cb)
101 return;
102 if(lev == JZ_LOG_IGNORE)
103 return;
104 if(lev > jz->log_level)
105 return;
106
107 va_list ap;
108
109 va_start(ap, fmt);
110 int n = vsnprintf(NULL, 0, fmt, ap);
111 va_end(ap);
112
113 if(n < 0)
114 return;
115
116 char* buf = malloc(n + 1);
117 if(!buf)
118 return;
119
120 va_start(ap, fmt);
121 n = vsnprintf(buf, n + 1, fmt, ap);
122 va_end(ap);
123
124 if(n >= 0)
125 jz->log_cb(lev, buf);
126
127 free(buf);
128}
129
130/** \brief Log callback which writes messages to `stderr`.
131 */
132void jz_log_cb_stderr(jz_log_level lev, const char* msg)
133{
134 static const char* const tags[] =
135 {"ERROR", "WARNING", "NOTICE", "DETAIL", "DEBUG"};
136 fprintf(stderr, "[%7s] %s\n", tags[lev], msg);
137 fflush(stderr);
138}
139
140/** \brief Sleep for `ms` milliseconds.
141 */
142void jz_sleepms(int ms)
143{
144#ifdef WIN32
145 Sleep(ms);
146#else
147 struct timespec ts;
148 long ns = ms % 1000;
149 ts.tv_nsec = ns * 1000 * 1000;
150 ts.tv_sec = ms / 1000;
151 nanosleep(&ts, NULL);
152#endif
153}
154
155/** \brief Add reference to libusb context, allocating it if necessary */
156int jz_context_ref_libusb(jz_context* jz)
157{
158 if(jz->usb_ctxref == 0) {
159 int rc = libusb_init(&jz->usb_ctx);
160 if(rc < 0) {
161 jz_log(jz, JZ_LOG_ERROR, "libusb_init: %s", libusb_strerror(rc));
162 return JZ_ERR_USB;
163 }
164 }
165
166 jz->usb_ctxref += 1;
167 return JZ_SUCCESS;
168}
169
170/** \brief Remove reference to libusb context, freeing if it hits zero */
171void jz_context_unref_libusb(jz_context* jz)
172{
173 if(--jz->usb_ctxref == 0) {
174 libusb_exit(jz->usb_ctx);
175 jz->usb_ctx = NULL;
176 }
177}
diff --git a/utils/jztool/src/device_info.c b/utils/jztool/src/device_info.c
new file mode 100644
index 0000000000..cc431959ca
--- /dev/null
+++ b/utils/jztool/src/device_info.c
@@ -0,0 +1,109 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <string.h>
24
25static const jz_device_info infotable[JZ_NUM_DEVICES] = {
26 [JZ_DEVICE_FIIOM3K] = {
27 .name = "fiiom3k",
28 .file_ext = "m3k",
29 .description = "FiiO M3K",
30 .device_type = JZ_DEVICE_FIIOM3K,
31 .cpu_type = JZ_CPU_X1000,
32 .vendor_id = 0x2972,
33 .product_id = 0x0003,
34 },
35 [JZ_DEVICE_SHANLINGQ1] = {
36 .name = "shanlingq1",
37 .file_ext = "q1",
38 .description = "Shanling Q1",
39 .device_type = JZ_DEVICE_SHANLINGQ1,
40 .cpu_type = JZ_CPU_X1000,
41 .vendor_id = 0x0525,
42 .product_id = 0xa4a5,
43 },
44 [JZ_DEVICE_EROSQ] = {
45 .name = "erosq",
46 .file_ext = "erosq",
47 .description = "AIGO Eros Q",
48 .device_type = JZ_DEVICE_EROSQ,
49 .cpu_type = JZ_CPU_X1000,
50 .vendor_id = 0xc502,
51 .product_id = 0x0023,
52 },
53};
54
55static const jz_cpu_info cputable[JZ_NUM_CPUS] = {
56 [JZ_CPU_X1000] = {
57 .info_str = "X1000_v1",
58 .vendor_id = 0xa108,
59 .product_id = 0x1000,
60 .stage1_load_addr = 0xf4001000,
61 .stage1_exec_addr = 0xf4001800,
62 .stage2_load_addr = 0x80004000,
63 .stage2_exec_addr = 0x80004000,
64 },
65};
66
67/** \brief Lookup info for a device by type, returns NULL if not found. */
68const jz_device_info* jz_get_device_info(jz_device_type type)
69{
70 return jz_get_device_info_indexed(type);
71}
72
73/** \brief Lookup info for a device by name, returns NULL if not found. */
74const jz_device_info* jz_get_device_info_named(const char* name)
75{
76 for(int i = 0; i < JZ_NUM_DEVICES; ++i)
77 if(!strcmp(infotable[i].name, name))
78 return &infotable[i];
79
80 return NULL;
81}
82
83/** \brief Get a device info entry by index, returns NULL if out of range. */
84const jz_device_info* jz_get_device_info_indexed(int index)
85{
86 if(index < JZ_NUM_DEVICES)
87 return &infotable[index];
88 else
89 return NULL;
90}
91
92/** \brief Lookup info for a CPU, returns NULL if not found. */
93const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type)
94{
95 if(type < JZ_NUM_CPUS)
96 return &cputable[type];
97 else
98 return NULL;
99}
100
101/** \brief Lookup info for a CPU by info string, returns NULL if not found. */
102const jz_cpu_info* jz_get_cpu_info_named(const char* info_str)
103{
104 for(int i = 0; i < JZ_NUM_CPUS; ++i)
105 if(!strcmp(cputable[i].info_str, info_str))
106 return &cputable[i];
107
108 return NULL;
109}
diff --git a/utils/jztool/src/identify_file.c b/utils/jztool/src/identify_file.c
new file mode 100644
index 0000000000..e475d98a3b
--- /dev/null
+++ b/utils/jztool/src/identify_file.c
@@ -0,0 +1,170 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <string.h>
24
25/* Following is copied from mkspl-x1000, basically */
26struct x1000_spl_header {
27 uint8_t magic[8];
28 uint8_t type;
29 uint8_t crc7;
30 uint8_t ppb;
31 uint8_t bpp;
32 uint32_t length;
33};
34
35static const uint8_t x1000_spl_header_magic[8] =
36 {0x06, 0x05, 0x04, 0x03, 0x02, 0x55, 0xaa, 0x55};
37
38static const size_t X1000_SPL_HEADER_SIZE = 2 * 1024;
39
40static uint8_t crc7(const uint8_t* buf, size_t len)
41{
42 /* table-based computation of CRC7 */
43 static const uint8_t t[256] = {
44 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f,
45 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
46 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26,
47 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
48 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d,
49 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
50 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14,
51 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
52 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b,
53 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
54 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42,
55 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
56 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69,
57 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
58 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70,
59 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
60 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e,
61 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
62 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67,
63 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
64 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
65 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
66 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55,
67 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
68 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a,
69 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
70 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03,
71 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
72 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28,
73 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
74 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31,
75 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
76 };
77
78 uint8_t crc = 0;
79 while(len--)
80 crc = t[(crc << 1) ^ *buf++];
81 return crc;
82}
83
84/** \brief Identify a file as an SPL for X1000 CPUs
85 * \param data File data buffer
86 * \param len Length of file
87 * \return JZ_SUCCESS if file looks correct, or one of the following errors
88 * \retval JZ_IDERR_WRONG_SIZE file too small or size doesn't match header
89 * \retval JZ_IDERR_BAD_HEADER missing magic bytes from header
90 * \retval JZ_IDERR_BAD_CHECKSUM CRC7 mismatch
91 */
92int jz_identify_x1000_spl(const void* data, size_t len)
93{
94 /* Use <= check because a header-only file is not really valid,
95 * it should have at least one byte in it... */
96 if(len <= X1000_SPL_HEADER_SIZE)
97 return JZ_IDERR_WRONG_SIZE;
98
99 /* Look for header magic bytes */
100 const struct x1000_spl_header* header = (const struct x1000_spl_header*)data;
101 if(memcmp(header->magic, x1000_spl_header_magic, 8))
102 return JZ_IDERR_BAD_HEADER;
103
104 /* Length stored in the header should equal the length of the file */
105 if(header->length != len)
106 return JZ_IDERR_WRONG_SIZE;
107
108 /* Compute the CRC7 checksum; it only covers the SPL code */
109 const uint8_t* dat = (const uint8_t*)data;
110 uint8_t sum = crc7(&dat[X1000_SPL_HEADER_SIZE], len - X1000_SPL_HEADER_SIZE);
111 if(header->crc7 != sum)
112 return JZ_IDERR_BAD_CHECKSUM;
113
114 return JZ_SUCCESS;
115
116}
117
118static const struct scramble_model_info {
119 const char* name;
120 int model_num;
121 size_t offset_crc;
122 size_t offset_name;
123 size_t offset_data;
124} scramble_models[] = {
125 {"fiio", 114, 0, 4, 8},
126 {"shq1", 115, 0, 4, 8},
127 {"eros", 116, 0, 4, 8},
128 {NULL, 0, 0, 0, 0},
129};
130
131/** \brief Identify a file as a Rockbox `scramble` image
132 * \param data File data buffer
133 * \param len Length of file
134 * \return JZ_SUCCESS if file looks correct, or one of the following errors
135 * \retval JZ_IDERR_UNRECOGNIZED_MODEL unsupported/unknown model type
136 * \retval JZ_IDERR_BAD_CHECKSUM checksum mismatch
137 */
138int jz_identify_scramble_image(const void* data, size_t len)
139{
140 const uint8_t* dat;
141 const struct scramble_model_info* model_info;
142 uint32_t sum, file_sum;
143
144 dat = (const uint8_t*)data;
145 model_info = &scramble_models[0];
146
147 /* Look up the model number */
148 for(; model_info->name != NULL; ++model_info) {
149 if(model_info->offset_name + 4 > len)
150 continue;
151 if(!memcmp(&dat[model_info->offset_name], model_info->name, 4))
152 break;
153 }
154
155 if(model_info->name == NULL)
156 return JZ_IDERR_UNRECOGNIZED_MODEL;
157
158 /* Compute the checksum */
159 sum = model_info->model_num;
160 for(size_t i = model_info->offset_data; i < len; ++i)
161 sum += dat[i];
162
163 /* Compare with file's checksum, it's stored in big-endian form */
164 dat += model_info->offset_crc;
165 file_sum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
166 if(sum != file_sum)
167 return JZ_IDERR_BAD_CHECKSUM;
168
169 return JZ_SUCCESS;
170}
diff --git a/utils/jztool/src/jztool_private.h b/utils/jztool/src/jztool_private.h
new file mode 100644
index 0000000000..11299f21f9
--- /dev/null
+++ b/utils/jztool/src/jztool_private.h
@@ -0,0 +1,44 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef JZTOOL_PRIVATE_H
23#define JZTOOL_PRIVATE_H
24
25#include "jztool.h"
26#include <libusb.h>
27
28struct jz_context {
29 void* user_data;
30 jz_log_cb log_cb;
31 jz_log_level log_level;
32 libusb_context* usb_ctx;
33 int usb_ctxref;
34};
35
36struct jz_usbdev {
37 jz_context* jz;
38 libusb_device_handle* handle;
39};
40
41int jz_context_ref_libusb(jz_context* jz);
42void jz_context_unref_libusb(jz_context* jz);
43
44#endif /* JZTOOL_PRIVATE_H */
diff --git a/utils/jztool/src/ucl_unpack.c b/utils/jztool/src/ucl_unpack.c
new file mode 100644
index 0000000000..3b199c7008
--- /dev/null
+++ b/utils/jztool/src/ucl_unpack.c
@@ -0,0 +1,128 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include "ucl/ucl.h"
24
25static uint32_t xread32(const uint8_t* d)
26{
27 uint32_t r = 0;
28 r |= d[0] << 24;
29 r |= d[1] << 16;
30 r |= d[2] << 8;
31 r |= d[3] << 0;
32 return r;
33}
34
35/* adapted from firmware/common/ucl_decompress.c */
36jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len)
37{
38 static const uint8_t magic[8] =
39 {0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a};
40
41 jz_buffer* buffer = NULL;
42
43 /* make sure there are enough bytes for the header */
44 if(src_len < 18)
45 goto error;
46
47 /* avoid memcmp for reasons of code size */
48 for(size_t i = 0; i < sizeof(magic); ++i)
49 if(src[i] != magic[i])
50 goto error;
51
52 /* read the other header fields */
53 /* uint32_t flags = xread32(&src[8]); */
54 uint8_t method = src[12];
55 /* uint8_t level = src[13]; */
56 uint32_t block_size = xread32(&src[14]);
57
58 /* check supported compression method */
59 if(method != 0x2e)
60 goto error;
61
62 /* validate */
63 if(block_size < 1024 || block_size > 8*1024*1024)
64 goto error;
65
66 src += 18;
67 src_len -= 18;
68
69 /* Calculate amount of space that we might need & allocate a buffer:
70 * - subtract 4 to account for end of file marker
71 * - each block is block_size bytes + 8 bytes of header
72 * - add one to nr_blocks to account for case where file size < block size
73 * - total size = max uncompressed size of block * nr_blocks
74 */
75 uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1;
76 uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256);
77 buffer = jz_buffer_alloc(max_size, NULL);
78 if(!buffer)
79 goto error;
80
81 /* perform the decompression */
82 uint32_t dst_ilen = buffer->size;
83 uint8_t* dst = buffer->data;
84 while(1) {
85 if(src_len < 4)
86 goto error;
87
88 uint32_t out_len = xread32(src); src += 4, src_len -= 4;
89 if(out_len == 0)
90 break;
91
92 if(src_len < 4)
93 goto error;
94
95 uint32_t in_len = xread32(src); src += 4, src_len -= 4;
96 if(in_len > block_size || out_len > block_size ||
97 in_len == 0 || in_len > out_len)
98 goto error;
99
100 if(src_len < in_len)
101 goto error;
102
103 if(in_len < out_len) {
104 uint32_t actual_out_len = dst_ilen;
105 int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL);
106 if(rc != UCL_E_OK)
107 goto error;
108 if(actual_out_len != out_len)
109 goto error;
110 } else {
111 for(size_t i = 0; i < in_len; ++i)
112 dst[i] = src[i];
113 }
114
115 src += in_len;
116 src_len -= in_len;
117 dst += out_len;
118 dst_ilen -= out_len;
119 }
120
121 /* subtract leftover number of bytes to get size of compressed output */
122 *dst_len = buffer->size - dst_ilen;
123 return buffer;
124
125 error:
126 jz_buffer_free(buffer);
127 return NULL;
128}
diff --git a/utils/jztool/src/usb.c b/utils/jztool/src/usb.c
new file mode 100644
index 0000000000..cfc3ba60cb
--- /dev/null
+++ b/utils/jztool/src/usb.c
@@ -0,0 +1,291 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool_private.h"
23#include <stdlib.h>
24#include <stdbool.h>
25#include <string.h>
26
27#define VR_GET_CPU_INFO 0
28#define VR_SET_DATA_ADDRESS 1
29#define VR_SET_DATA_LENGTH 2
30#define VR_FLUSH_CACHES 3
31#define VR_PROGRAM_START1 4
32#define VR_PROGRAM_START2 5
33
34/** \brief Open a USB device
35 * \param jz Context
36 * \param devptr Returns pointer to the USB device upon success
37 * \param vend_id USB vendor ID
38 * \param prod_id USB product ID
39 * \return either JZ_SUCCESS if device was opened, or an error below
40 * \retval JZ_ERR_OUT_OF_MEMORY malloc failed
41 * \retval JZ_ERR_USB libusb error (details are logged)
42 * \retval JZ_ERR_NO_DEVICE can't unambiguously find the device
43 */
44int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id)
45{
46 int rc;
47 jz_usbdev* dev = NULL;
48 libusb_device_handle* usb_handle = NULL;
49 libusb_device** dev_list = NULL;
50 ssize_t dev_index = -1, dev_count;
51
52 rc = jz_context_ref_libusb(jz);
53 if(rc < 0)
54 return rc;
55
56 dev = malloc(sizeof(struct jz_usbdev));
57 if(!dev) {
58 rc = JZ_ERR_OUT_OF_MEMORY;
59 goto error;
60 }
61
62 dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list);
63 if(dev_count < 0) {
64 jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count));
65 rc = JZ_ERR_USB;
66 goto error;
67 }
68
69 for(ssize_t i = 0; i < dev_count; ++i) {
70 struct libusb_device_descriptor desc;
71 rc = libusb_get_device_descriptor(dev_list[i], &desc);
72 if(rc < 0) {
73 jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s",
74 libusb_strerror(rc));
75 continue;
76 }
77
78 if(desc.idVendor != vend_id || desc.idProduct != prod_id)
79 continue;
80
81 if(dev_index >= 0) {
82 /* not the best, but it is the safest thing */
83 jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x",
84 (unsigned int)vend_id, (unsigned int)prod_id);
85 jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again");
86 rc = JZ_ERR_NO_DEVICE;
87 goto error;
88 }
89
90 dev_index = i;
91 }
92
93 if(dev_index < 0) {
94 jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%04x found",
95 (unsigned int)vend_id, (unsigned int)prod_id);
96 rc = JZ_ERR_NO_DEVICE;
97 goto error;
98 }
99
100 rc = libusb_open(dev_list[dev_index], &usb_handle);
101 if(rc < 0) {
102 jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc));
103 rc = JZ_ERR_USB;
104 goto error;
105 }
106
107 rc = libusb_claim_interface(usb_handle, 0);
108 if(rc < 0) {
109 jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc));
110 rc = JZ_ERR_USB;
111 goto error;
112 }
113
114 jz_log(jz, JZ_LOG_DEBUG, "Opened device (%p, ID %04x:%04x)",
115 dev, (unsigned int)vend_id, (unsigned int)prod_id);
116 dev->jz = jz;
117 dev->handle = usb_handle;
118 *devptr = dev;
119 rc = JZ_SUCCESS;
120
121 exit:
122 if(dev_list)
123 libusb_free_device_list(dev_list, true);
124 return rc;
125
126 error:
127 if(dev)
128 free(dev);
129 if(usb_handle)
130 libusb_close(usb_handle);
131 jz_context_unref_libusb(jz);
132 goto exit;
133}
134
135/** \brief Close a USB device
136 * \param dev Device to close; memory will be freed automatically
137 */
138void jz_usb_close(jz_usbdev* dev)
139{
140 jz_log(dev->jz, JZ_LOG_DEBUG, "Closing device (%p)", dev);
141 libusb_release_interface(dev->handle, 0);
142 libusb_close(dev->handle);
143 jz_context_unref_libusb(dev->jz);
144 free(dev);
145}
146
147// Does an Ingenic-specific vendor request
148// Written with X1000 in mind but other Ingenic CPUs have the same commands
149static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg,
150 void* buffer, int buflen)
151{
152 int rc = libusb_control_transfer(dev->handle,
153 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
154 req, arg >> 16, arg & 0xffff, buffer, buflen, 1000);
155
156 if(rc < 0) {
157 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc));
158 rc = JZ_ERR_USB;
159 } else {
160 static const char* req_names[] = {
161 "GET_CPU_INFO",
162 "SET_DATA_ADDRESS",
163 "SET_DATA_LENGTH",
164 "FLUSH_CACHES",
165 "PROGRAM_START1",
166 "PROGRAM_START2",
167 };
168
169 jz_log(dev->jz, JZ_LOG_DEBUG, "Issued %s %08lu",
170 req_names[req], (unsigned long)arg);
171 rc = JZ_SUCCESS;
172 }
173
174 return rc;
175}
176
177// Bulk transfer wrapper
178static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf)
179{
180 int xfered = 0;
181 int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1;
182 int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000);
183
184 if(rc < 0) {
185 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc));
186 rc = JZ_ERR_USB;
187 } else if(xfered != (int)len) {
188 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered");
189 rc = JZ_ERR_USB;
190 } else {
191 jz_log(dev->jz, JZ_LOG_DEBUG, "Transferred %zu bytes %s",
192 len, write ? "to device" : "from device");
193 rc = JZ_SUCCESS;
194 }
195
196 return rc;
197}
198
199// Memory send/receive primitive, performs the necessary vendor requests
200// and then tranfers data using the bulk endpoint
201static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr,
202 size_t len, void* data)
203{
204 int rc;
205 rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr, NULL, 0);
206 if(rc < 0)
207 return rc;
208
209 rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0);
210 if(rc < 0)
211 return rc;
212
213 return jz_usb_transfer(dev, write, len, data);
214}
215
216/** \brief Write data to device memory
217 * \param dev USB device
218 * \param addr Address where data should be written
219 * \param len Length of the data, in bytes, should be positive
220 * \param data Data buffer
221 * \return either JZ_SUCCESS on success or a failure code
222 */
223int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data)
224{
225 return jz_usb_sendrecv(dev, true, addr, len, (void*)data);
226}
227
228/** \brief Read data to device memory
229 * \param dev USB device
230 * \param addr Address to read from
231 * \param len Length of the data, in bytes, should be positive
232 * \param data Data buffer
233 * \return either JZ_SUCCESS on success or a failure code
234 */
235int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data)
236{
237 return jz_usb_sendrecv(dev, false, addr, len, data);
238}
239
240/** \brief Execute stage1 program jumping to the specified address
241 * \param dev USB device
242 * \param addr Address to begin execution at
243 * \return either JZ_SUCCESS on success or a failure code
244 */
245int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
246{
247 return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0);
248}
249
250/** \brief Execute stage2 program jumping to the specified address
251 * \param dev USB device
252 * \param addr Address to begin execution at
253 * \return either JZ_SUCCESS on success or a failure code
254 */
255int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
256{
257 return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0);
258}
259
260/** \brief Ask device to flush CPU caches
261 * \param dev USB device
262 * \return either JZ_SUCCESS on success or a failure code
263 */
264int jz_usb_flush_caches(jz_usbdev* dev)
265{
266 return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0);
267}
268
269/** \brief Ask device for CPU info string
270 * \param dev USB device
271 * \param buffer Buffer to hold the info string
272 * \param buflen Size of the buffer, in bytes
273 * \return either JZ_SUCCESS on success or a failure code
274 *
275 * The buffer will always be null terminated, but to ensure the info string is
276 * not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long.
277 */
278int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen)
279{
280 char tmpbuf[JZ_CPUINFO_BUFLEN];
281 int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8);
282 if(rc != JZ_SUCCESS)
283 return rc;
284
285 if(buflen > 0) {
286 strncpy(buffer, tmpbuf, buflen);
287 buffer[buflen - 1] = 0;
288 }
289
290 return JZ_SUCCESS;
291}
diff --git a/utils/jztool/src/x1000.c b/utils/jztool/src/x1000.c
new file mode 100644
index 0000000000..f59727a2ca
--- /dev/null
+++ b/utils/jztool/src/x1000.c
@@ -0,0 +1,180 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include "jztool_private.h"
24#include "microtar-stdio.h"
25#include <stdbool.h>
26#include <string.h>
27
28/* TODO: these functions could be refactored to be CPU-agnostic */
29static int run_stage1(jz_usbdev* dev, jz_buffer* buf)
30{
31 int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data);
32 if(rc < 0)
33 return rc;
34
35 return jz_usb_start1(dev, 0xf4001800);
36}
37
38static int run_stage2(jz_usbdev* dev, jz_buffer* buf)
39{
40 int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data);
41 if(rc < 0)
42 return rc;
43
44 rc = jz_usb_flush_caches(dev);
45 if(rc < 0)
46 return rc;
47
48 return jz_usb_start2(dev, 0x80004000);
49}
50
51static int get_file(jz_context* jz, mtar_t* tar, const char* file,
52 bool decompress, jz_buffer** buf)
53{
54 jz_buffer* buffer = NULL;
55 const mtar_header_t* h;
56 int rc;
57
58 rc = mtar_find(tar, file);
59 if(rc != MTAR_ESUCCESS) {
60 jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc);
61 return JZ_ERR_BAD_FILE_FORMAT;
62 }
63
64 h = mtar_get_header(tar);
65 buffer = jz_buffer_alloc(h->size, NULL);
66 if(!buffer)
67 return JZ_ERR_OUT_OF_MEMORY;
68
69 rc = mtar_read_data(tar, buffer->data, buffer->size);
70 if(rc < 0 || (unsigned)rc != buffer->size) {
71 jz_buffer_free(buffer);
72 jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", file, rc);
73 return JZ_ERR_BAD_FILE_FORMAT;
74 }
75
76 if(decompress) {
77 uint32_t dst_len;
78 jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len);
79 jz_buffer_free(buffer);
80 if(!nbuf) {
81 jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file);
82 return JZ_ERR_BAD_FILE_FORMAT;
83 }
84
85 /* for simplicity just forget original size of buffer */
86 nbuf->size = dst_len;
87 buffer = nbuf;
88 }
89
90 *buf = buffer;
91 return JZ_SUCCESS;
92}
93
94static int show_version(jz_context* jz, jz_buffer* info_file)
95{
96 /* Extract the version string and log it for informational purposes */
97 char* boot_version = (char*)info_file->data;
98 char* endpos = memchr(boot_version, '\n', info_file->size);
99 if(!endpos) {
100 jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file");
101 return JZ_ERR_BAD_FILE_FORMAT;
102 }
103
104 *endpos = 0;
105 jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version);
106 return JZ_SUCCESS;
107}
108
109/** \brief Load the Rockbox bootloader on an X1000 device
110 * \param dev USB device freshly returned by jz_usb_open()
111 * \param filename Path to the "bootloader.target" file
112 * \return either JZ_SUCCESS or an error code
113 */
114int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename)
115{
116 const jz_device_info* dev_info;
117 char spl_filename[32];
118 jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
119 mtar_t tar;
120 int rc;
121
122 /* In retrospect using a model-dependent archive format was not a good
123 * idea, but it's not worth fixing just yet... */
124 dev_info = jz_get_device_info(type);
125 if(!dev_info)
126 return JZ_ERR_OTHER;
127 /* use of sprintf is safe since file_ext is short */
128 sprintf(spl_filename, "spl.%s", dev_info->file_ext);
129
130 /* Now open the archive */
131 rc = mtar_open(&tar, filename, "rb");
132 if(rc != MTAR_ESUCCESS) {
133 jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
134 return JZ_ERR_OPEN_FILE;
135 }
136
137 /* Extract all necessary files */
138 rc = get_file(dev->jz, &tar, spl_filename, false, &spl);
139 if(rc != JZ_SUCCESS)
140 goto error;
141
142 rc = get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
143 if(rc != JZ_SUCCESS)
144 goto error;
145
146 rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
147 if(rc != JZ_SUCCESS)
148 goto error;
149
150 /* Display the version string */
151 rc = show_version(dev->jz, info_file);
152 if(rc != JZ_SUCCESS)
153 goto error;
154
155 /* Stage1 boot of SPL to set up hardware */
156 rc = run_stage1(dev, spl);
157 if(rc != JZ_SUCCESS)
158 goto error;
159
160 /* Need a bit of time for SPL to handle init */
161 jz_sleepms(500);
162
163 /* Stage2 boot into the bootloader's recovery menu
164 * User has to take manual action from there */
165 rc = run_stage2(dev, bootloader);
166 if(rc != JZ_SUCCESS)
167 goto error;
168
169 rc = JZ_SUCCESS;
170
171 error:
172 if(spl)
173 jz_buffer_free(spl);
174 if(bootloader)
175 jz_buffer_free(bootloader);
176 if(info_file)
177 jz_buffer_free(info_file);
178 mtar_close(&tar);
179 return rc;
180}