diff options
Diffstat (limited to 'utils/jztool')
-rw-r--r-- | utils/jztool/Makefile | 47 | ||||
-rw-r--r-- | utils/jztool/README.md | 135 | ||||
-rw-r--r-- | utils/jztool/include/jztool.h | 202 | ||||
-rw-r--r-- | utils/jztool/jztool.c | 212 | ||||
-rw-r--r-- | utils/jztool/src/buffer.c | 134 | ||||
-rw-r--r-- | utils/jztool/src/context.c | 177 | ||||
-rw-r--r-- | utils/jztool/src/device_info.c | 109 | ||||
-rw-r--r-- | utils/jztool/src/identify_file.c | 170 | ||||
-rw-r--r-- | utils/jztool/src/jztool_private.h | 44 | ||||
-rw-r--r-- | utils/jztool/src/ucl_unpack.c | 128 | ||||
-rw-r--r-- | utils/jztool/src/usb.c | 291 | ||||
-rw-r--r-- | utils/jztool/src/x1000.c | 180 |
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 | |||
8 | CFLAGS += -Wall -Wextra -Iinclude -I../../tools/ucl/include -I../../lib/microtar/src | ||
9 | OUTPUT = jztool | ||
10 | |||
11 | ifdef RELEASE | ||
12 | CFLAGS += -Os -DNDEBUG | ||
13 | else | ||
14 | CFLAGS += -O0 -ggdb | ||
15 | endif | ||
16 | |||
17 | LIBSOURCES := 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 | ||
19 | SOURCES := $(LIBSOURCES) jztool.c | ||
20 | EXTRADEPS := libucl.a libmicrotar.a | ||
21 | |||
22 | CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -) | ||
23 | |||
24 | ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32) | ||
25 | # TODO: support Windows | ||
26 | else | ||
27 | ifeq ($(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. | ||
30 | LIBUSB_CFLAGS ?= -I/usr/local/include/libusb-1.0 | ||
31 | ifdef STATIC | ||
32 | LIBUSB_LDOPTS ?= /usr/local/lib/libusb-1.0.a -framework IOKit -framework CoreFoundation | ||
33 | else | ||
34 | LIBUSB_LDOPTS ?= -L/usr/local/lib -lusb-1.0 | ||
35 | endif | ||
36 | else | ||
37 | # Linux; note for static builds you need to build a copy of libusb without | ||
38 | # udev support and specify the includes / libs manually | ||
39 | LIBUSB_CFLAGS ?= `pkg-config --cflags libusb-1.0` | ||
40 | LIBUSB_LDOPTS ?= `pkg-config --libs libusb-1.0` | ||
41 | endif | ||
42 | endif | ||
43 | |||
44 | CFLAGS += $(LIBUSB_CFLAGS) | ||
45 | LDOPTS += $(LIBUSB_LDOPTS) | ||
46 | |||
47 | include ../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 | |||
3 | The `jztool` utility can help install, backup, and restore the bootloader on | ||
4 | Rockbox players based on a supported Ingenic SoC (currently only the X1000). | ||
5 | |||
6 | ## Running jztool | ||
7 | |||
8 | ### Getting a bootloader | ||
9 | |||
10 | To use `jztool` you need to compile or download a bootloader for your player. | ||
11 | It's recommended to use only official released bootloaders, since bootloaders | ||
12 | compiled from Git are not tested and might be buggy. | ||
13 | |||
14 | You can download released bootloaders from <https://download.rockbox.org/>. | ||
15 | |||
16 | The bootloader file is named after the target: for example, the FiiO M3K | ||
17 | bootloader is called `bootloader.m3k`. The FiiO M3K is used as an example | ||
18 | here, but the instructions apply to all X1000-based players. | ||
19 | |||
20 | Use `jztool --help` to find out the model name of your player. | ||
21 | |||
22 | ### Entering USB boot mode | ||
23 | |||
24 | USB boot mode is a low-level mode provided by the CPU which allows a computer | ||
25 | to load firmware onto the device. You need to put your player into this mode | ||
26 | manually before using `jztool` (unfortunately, it can't be done automatically.) | ||
27 | |||
28 | To connect the player in USB boot mode, follow these steps: | ||
29 | |||
30 | 1. Ensure the player is fully powered off. | ||
31 | 2. Plug one end of the USB cable into your player. | ||
32 | 3. Hold down your player's USB boot key (see below). | ||
33 | 4. Plug the other end of the USB cable into your computer. | ||
34 | 5. Let go of the USB boot key. | ||
35 | |||
36 | The 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 | |||
44 | Run the following command in a terminal. Note that on Linux, you will need to | ||
45 | have 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 | |||
56 | To allow `jztool` access to your player in USB boot mode, you need to install | ||
57 | the WinUSB driver. The recommended way to install it is using Zadig, which | ||
58 | may be downloaded from its homepage <https://zadig.akeo.ie>. Please note | ||
59 | this is 3rd party software not maintained or supported by Rockbox developers. | ||
60 | (Zadig will require administrator access on the machine you are using.) | ||
61 | |||
62 | When running Zadig you must select the WinUSB driver; the other driver options | ||
63 | will not work properly with `jztool`. You will have to select the correct USB | ||
64 | device in Zadig. All X1000-based players use the same USB ID while in USB boot | ||
65 | mode, listed below. NOTE: the device name may show only as "X" and a hollow | ||
66 | square in Zadig. The IDs will not change, so those are the most reliable way | ||
67 | to confirm you have selected the correct device. | ||
68 | |||
69 | ``` | ||
70 | Name: Ingenic Semiconductor Co.,Ltd X1000 | ||
71 | USB ID: A108 1000 | ||
72 | ``` | ||
73 | |||
74 | Assuming you installed the WinUSB driver successfully, open a command prompt | ||
75 | in the folder containing `jztool`. Administrator access is not required for | ||
76 | this step. | ||
77 | |||
78 | Type 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 | |||
87 | If `jztool` runs successfully your player will display the Rockbox bootloader's | ||
88 | recovery menu. If you want to permanently install Rockbox to your device, copy | ||
89 | the bootloader file you downloaded to the root of your SD card, insert the SD | ||
90 | card to your player, and choose "Install/update bootloader" from the menu. | ||
91 | |||
92 | It is _highly_ recommended that you take a backup of your existing bootloader | ||
93 | in case of any trouble -- choose "Backup bootloader" from the recovery menu. | ||
94 | The backup file is called `PLAYER-boot.bin`, where `PLAYER` is the model name. | ||
95 | (Example: `fiiom3k-boot.bin`.) | ||
96 | |||
97 | You can restore the backup later by putting it on the root of your SD card and | ||
98 | selecting "Restor bootloader" in the recovery menu. | ||
99 | |||
100 | After installing the Rockbox bootloader, you can access the recovery menu by | ||
101 | holding 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 | |||
118 | There's only a bare minimum of documentation, and logging is sparse, not | ||
119 | really enough to debug problems. | ||
120 | |||
121 | Some of the error messages could be friendlier too. | ||
122 | |||
123 | ### Integration with the Rockbox utility | ||
124 | |||
125 | Adding support to the Rockbox utility should be mostly boilerplate since the | ||
126 | jztool library wraps all the troublesome details. | ||
127 | |||
128 | Permissions 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 | ||
130 | a udev rule to allow access to the specific USB IDs we need, eg. allowing | ||
131 | users in the "wheel" group to access the device. | ||
132 | |||
133 | On Windows and Mac, no special permissions are needed to access USB devices | ||
134 | assuming the drivers are set up. (Zadig does require administrator access | ||
135 | to 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 | ||
29 | extern "C" { | ||
30 | #endif | ||
31 | |||
32 | /****************************************************************************** | ||
33 | * Types, enumerations, etc | ||
34 | */ | ||
35 | |||
36 | #define JZ_CPUINFO_BUFLEN 9 | ||
37 | |||
38 | typedef struct jz_context jz_context; | ||
39 | typedef struct jz_usbdev jz_usbdev; | ||
40 | typedef struct jz_device_info jz_device_info; | ||
41 | typedef struct jz_cpu_info jz_cpu_info; | ||
42 | typedef struct jz_buffer jz_buffer; | ||
43 | |||
44 | typedef enum jz_error jz_error; | ||
45 | typedef enum jz_identify_error jz_identify_error; | ||
46 | typedef enum jz_log_level jz_log_level; | ||
47 | typedef enum jz_device_type jz_device_type; | ||
48 | typedef enum jz_cpu_type jz_cpu_type; | ||
49 | |||
50 | typedef void(*jz_log_cb)(jz_log_level, const char*); | ||
51 | |||
52 | enum 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 | |||
64 | enum 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 | |||
72 | enum 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 | |||
81 | enum jz_device_type { | ||
82 | JZ_DEVICE_FIIOM3K, | ||
83 | JZ_DEVICE_SHANLINGQ1, | ||
84 | JZ_DEVICE_EROSQ, | ||
85 | JZ_NUM_DEVICES, | ||
86 | }; | ||
87 | |||
88 | enum jz_cpu_type { | ||
89 | JZ_CPU_X1000, | ||
90 | JZ_NUM_CPUS, | ||
91 | }; | ||
92 | |||
93 | struct 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 | |||
110 | struct 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 | |||
125 | struct jz_buffer { | ||
126 | size_t size; | ||
127 | uint8_t* data; | ||
128 | }; | ||
129 | |||
130 | /****************************************************************************** | ||
131 | * Library context and general functions | ||
132 | */ | ||
133 | |||
134 | jz_context* jz_context_create(void); | ||
135 | void jz_context_destroy(jz_context* jz); | ||
136 | |||
137 | void jz_context_set_user_data(jz_context* jz, void* ptr); | ||
138 | void* jz_context_get_user_data(jz_context* jz); | ||
139 | |||
140 | void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb); | ||
141 | void jz_context_set_log_level(jz_context* jz, jz_log_level lev); | ||
142 | |||
143 | void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...); | ||
144 | void jz_log_cb_stderr(jz_log_level lev, const char* msg); | ||
145 | |||
146 | void jz_sleepms(int ms); | ||
147 | |||
148 | /****************************************************************************** | ||
149 | * Device and file info | ||
150 | */ | ||
151 | |||
152 | const jz_device_info* jz_get_device_info(jz_device_type type); | ||
153 | const jz_device_info* jz_get_device_info_named(const char* name); | ||
154 | const jz_device_info* jz_get_device_info_indexed(int index); | ||
155 | |||
156 | const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type); | ||
157 | const jz_cpu_info* jz_get_cpu_info_named(const char* info_str); | ||
158 | |||
159 | int jz_identify_x1000_spl(const void* data, size_t len); | ||
160 | int jz_identify_scramble_image(const void* data, size_t len); | ||
161 | |||
162 | /****************************************************************************** | ||
163 | * USB boot ROM protocol | ||
164 | */ | ||
165 | |||
166 | int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id); | ||
167 | void jz_usb_close(jz_usbdev* dev); | ||
168 | |||
169 | int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data); | ||
170 | int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data); | ||
171 | int jz_usb_start1(jz_usbdev* dev, uint32_t addr); | ||
172 | int jz_usb_start2(jz_usbdev* dev, uint32_t addr); | ||
173 | int jz_usb_flush_caches(jz_usbdev* dev); | ||
174 | int 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 | |||
180 | int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename); | ||
181 | |||
182 | /****************************************************************************** | ||
183 | * Buffer API and other functions | ||
184 | */ | ||
185 | |||
186 | jz_buffer* jz_buffer_alloc(size_t size, const void* data); | ||
187 | void jz_buffer_free(jz_buffer* buf); | ||
188 | |||
189 | int jz_buffer_load(jz_buffer** buf, const char* filename); | ||
190 | int jz_buffer_save(jz_buffer* buf, const char* filename); | ||
191 | |||
192 | jz_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 | |||
28 | jz_context* jz = NULL; | ||
29 | jz_usbdev* usbdev = NULL; | ||
30 | const jz_device_info* dev_info = NULL; | ||
31 | const jz_cpu_info* cpu_info = NULL; | ||
32 | |||
33 | void 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 | |||
74 | int 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 | |||
96 | void 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 | |||
119 | void cleanup(void) | ||
120 | { | ||
121 | if(usbdev) | ||
122 | jz_usb_close(usbdev); | ||
123 | if(jz) | ||
124 | jz_context_destroy(jz); | ||
125 | } | ||
126 | |||
127 | int 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 | */ | ||
34 | jz_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 | */ | ||
55 | void 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 | */ | ||
71 | int 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 | */ | ||
115 | int 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 | */ | ||
37 | jz_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 */ | ||
49 | void 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. */ | ||
60 | void 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 */ | ||
66 | void* 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 | */ | ||
75 | void 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 | */ | ||
89 | void 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 | */ | ||
98 | void 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 | */ | ||
132 | void 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 | */ | ||
142 | void 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 */ | ||
156 | int 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 */ | ||
171 | void 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 | |||
25 | static 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 | |||
55 | static 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. */ | ||
68 | const 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. */ | ||
74 | const 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. */ | ||
84 | const 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. */ | ||
93 | const 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. */ | ||
102 | const 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 */ | ||
26 | struct 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 | |||
35 | static const uint8_t x1000_spl_header_magic[8] = | ||
36 | {0x06, 0x05, 0x04, 0x03, 0x02, 0x55, 0xaa, 0x55}; | ||
37 | |||
38 | static const size_t X1000_SPL_HEADER_SIZE = 2 * 1024; | ||
39 | |||
40 | static 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 | */ | ||
92 | int 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 | |||
118 | static 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 | */ | ||
138 | int 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 | |||
28 | struct 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 | |||
36 | struct jz_usbdev { | ||
37 | jz_context* jz; | ||
38 | libusb_device_handle* handle; | ||
39 | }; | ||
40 | |||
41 | int jz_context_ref_libusb(jz_context* jz); | ||
42 | void 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 | |||
25 | static 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 */ | ||
36 | jz_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 | */ | ||
44 | int 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 | */ | ||
138 | void 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 | ||
149 | static 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 | ||
178 | static 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 | ||
201 | static 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 | */ | ||
223 | int 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 | */ | ||
235 | int 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 | */ | ||
245 | int 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 | */ | ||
255 | int 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 | */ | ||
264 | int 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 | */ | ||
278 | int 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 */ | ||
29 | static 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 | |||
38 | static 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 | |||
51 | static 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 | |||
94 | static 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 | */ | ||
114 | int 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 | } | ||