diff options
author | Aidan MacDonald <amachronic@protonmail.com> | 2021-04-13 16:58:15 +0100 |
---|---|---|
committer | Aidan MacDonald <amachronic@protonmail.com> | 2021-04-17 20:23:19 +0000 |
commit | b41d53792c4c4e4abd6d810b2765d865775f5104 (patch) | |
tree | ae3e02b961c655cd49b3c13bc895a18547ceeec1 /rbutil/jztool | |
parent | 1b8542490da3283dfa0ce0f3363f16eab0609815 (diff) | |
download | rockbox-b41d53792c4c4e4abd6d810b2765d865775f5104.tar.gz rockbox-b41d53792c4c4e4abd6d810b2765d865775f5104.zip |
jztool: New utility for installing a bootloader on FiiO M3K
At present, this is just a command line tool for Linux only.
Integrating this with the Rockbox utility and porting to other
platforms should be straightforward; the README contains more
information.
Change-Id: Ie66fc837a02ab13c878925360cabc9805597548a
Diffstat (limited to 'rbutil/jztool')
-rw-r--r-- | rbutil/jztool/Makefile | 37 | ||||
-rw-r--r-- | rbutil/jztool/README.md | 60 | ||||
-rw-r--r-- | rbutil/jztool/include/jztool.h | 200 | ||||
-rw-r--r-- | rbutil/jztool/jztool.c | 227 | ||||
-rw-r--r-- | rbutil/jztool/src/buffer.c | 134 | ||||
-rw-r--r-- | rbutil/jztool/src/context.c | 167 | ||||
-rw-r--r-- | rbutil/jztool/src/device_info.c | 98 | ||||
-rw-r--r-- | rbutil/jztool/src/fiiom3k.c | 283 | ||||
-rw-r--r-- | rbutil/jztool/src/identify_file.c | 179 | ||||
-rw-r--r-- | rbutil/jztool/src/jztool_private.h | 44 | ||||
-rw-r--r-- | rbutil/jztool/src/paramlist.c | 135 | ||||
-rw-r--r-- | rbutil/jztool/src/usb.c | 203 | ||||
-rw-r--r-- | rbutil/jztool/src/x1000.c | 193 |
13 files changed, 1960 insertions, 0 deletions
diff --git a/rbutil/jztool/Makefile b/rbutil/jztool/Makefile new file mode 100644 index 0000000000..aa434adf1f --- /dev/null +++ b/rbutil/jztool/Makefile | |||
@@ -0,0 +1,37 @@ | |||
1 | # __________ __ ___. | ||
2 | # Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
3 | # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
4 | # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
5 | # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
6 | # \/ \/ \/ \/ \/ | ||
7 | |||
8 | CFLAGS += -Wall -Wextra -Iinclude | ||
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/fiiom3k.c src/identify_file.c src/paramlist.c \ | ||
19 | src/usb.c src/x1000.c | ||
20 | SOURCES := $(LIBSOURCES) jztool.c | ||
21 | EXTRADEPS := | ||
22 | |||
23 | CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -) | ||
24 | |||
25 | ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32) | ||
26 | # TODO: support Windows | ||
27 | else | ||
28 | ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE) | ||
29 | # TODO: support OSX | ||
30 | else | ||
31 | # Linux | ||
32 | CFLAGS += `pkg-config --cflags libusb-1.0` | ||
33 | LDOPTS += `pkg-config --libs libusb-1.0` | ||
34 | endif | ||
35 | endif | ||
36 | |||
37 | include ../libtools.make | ||
diff --git a/rbutil/jztool/README.md b/rbutil/jztool/README.md new file mode 100644 index 0000000000..6a9b78f8d7 --- /dev/null +++ b/rbutil/jztool/README.md | |||
@@ -0,0 +1,60 @@ | |||
1 | # jztool -- Ingenic device utility & bootloader installer | ||
2 | |||
3 | The `jztool` utility can install, backup, and restore the bootloader on | ||
4 | Rockbox players based on a supported Ingenic SoC. | ||
5 | |||
6 | ## FiiO M3K | ||
7 | |||
8 | To use `jztool` on the FiiO M3K you have to connect the player to your | ||
9 | computer in USB boot mode. | ||
10 | |||
11 | The easiest way to do this is by plugging in the microUSB cable to the M3K | ||
12 | and holding the volume down button while plugging the USB into your computer. | ||
13 | If you entered USB boot mode, the button light will turn on but the LCD will | ||
14 | turn off. | ||
15 | |||
16 | To install or update the Rockbox bootloader on the M3K, use the command | ||
17 | `jztool fiiom3k install`. It is recommended that you take a backup of your | ||
18 | current bootloader so you can restore it in case of any problems. | ||
19 | |||
20 | After any operation finishes, you will have to force a power off of the M3K | ||
21 | by holding down the power button for at least 10 seconds. This must be done | ||
22 | whether the operation succeeds or fails. Just don't power off or unplug the | ||
23 | device in the middle of an operation -- that might make bad things happen. | ||
24 | |||
25 | See `jztool --help` for info. | ||
26 | |||
27 | ## TODO list | ||
28 | |||
29 | ### Add better documentation and logging | ||
30 | |||
31 | There's only a bare minimum of documentation, and logging is sparse, not | ||
32 | really enough to debug problems. | ||
33 | |||
34 | Some of the error messages could be friendlier too. | ||
35 | |||
36 | ### Integration with the Rockbox utility | ||
37 | |||
38 | Adding support to the Rockbox utility should be mostly boilerplate since the | ||
39 | jztool library wraps all the troublesome details. | ||
40 | |||
41 | Getting appropriate privileges to access the USB device is the main issue. | ||
42 | Preferably, the Rockbox utility should not run as root/admin/etc. | ||
43 | |||
44 | - Windows: not sure | ||
45 | - Linux: needs udev rules or root privileges | ||
46 | - Mac: apparently does not need privileges | ||
47 | |||
48 | ### Porting to Windows | ||
49 | |||
50 | Windows wants to see a driver installed before we can access the USB device, | ||
51 | the easiest way to do this is by having the user run Zadig, a 3rd party app | ||
52 | which can install the WinUSB driver. WinUSB itself is from Microsoft and | ||
53 | bundled with Windows. | ||
54 | |||
55 | Zadig's homepage: https://zadig.akeo.ie/ | ||
56 | |||
57 | ### Porting to Mac | ||
58 | |||
59 | According to the libusb wiki, libusb works on Mac without any special setup or | ||
60 | privileges, presumably porting there is easy. | ||
diff --git a/rbutil/jztool/include/jztool.h b/rbutil/jztool/include/jztool.h new file mode 100644 index 0000000000..e16bc9765f --- /dev/null +++ b/rbutil/jztool/include/jztool.h | |||
@@ -0,0 +1,200 @@ | |||
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 | typedef struct jz_context jz_context; | ||
37 | typedef struct jz_usbdev jz_usbdev; | ||
38 | typedef struct jz_device_info jz_device_info; | ||
39 | typedef struct jz_buffer jz_buffer; | ||
40 | typedef struct jz_paramlist jz_paramlist; | ||
41 | |||
42 | typedef enum jz_error jz_error; | ||
43 | typedef enum jz_identify_error jz_identify_error; | ||
44 | typedef enum jz_log_level jz_log_level; | ||
45 | typedef enum jz_device_type jz_device_type; | ||
46 | typedef enum jz_cpu_type jz_cpu_type; | ||
47 | |||
48 | typedef void(*jz_log_cb)(jz_log_level, const char*); | ||
49 | typedef int(*jz_device_action_fn)(jz_context*, jz_paramlist*); | ||
50 | |||
51 | enum jz_error { | ||
52 | JZ_SUCCESS = 0, | ||
53 | JZ_ERR_OUT_OF_MEMORY = -1, | ||
54 | JZ_ERR_OPEN_FILE = -2, | ||
55 | JZ_ERR_FILE_IO = -3, | ||
56 | JZ_ERR_USB = -4, | ||
57 | JZ_ERR_NO_DEVICE = -5, | ||
58 | JZ_ERR_BAD_FILE_FORMAT = -6, | ||
59 | JZ_ERR_FLASH_ERROR = -7, | ||
60 | JZ_ERR_OTHER = -99, | ||
61 | }; | ||
62 | |||
63 | enum jz_identify_error { | ||
64 | JZ_IDERR_OTHER = -1, | ||
65 | JZ_IDERR_WRONG_SIZE = -2, | ||
66 | JZ_IDERR_BAD_HEADER = -3, | ||
67 | JZ_IDERR_BAD_CHECKSUM = -4, | ||
68 | JZ_IDERR_UNRECOGNIZED_MODEL = -5, | ||
69 | }; | ||
70 | |||
71 | enum jz_log_level { | ||
72 | JZ_LOG_IGNORE = -1, | ||
73 | JZ_LOG_ERROR = 0, | ||
74 | JZ_LOG_WARNING = 1, | ||
75 | JZ_LOG_NOTICE = 2, | ||
76 | JZ_LOG_DETAIL = 3, | ||
77 | JZ_LOG_DEBUG = 4, | ||
78 | }; | ||
79 | |||
80 | enum jz_device_type { | ||
81 | JZ_DEVICE_FIIOM3K, | ||
82 | }; | ||
83 | |||
84 | enum jz_cpu_type { | ||
85 | JZ_CPU_X1000, | ||
86 | }; | ||
87 | |||
88 | struct jz_device_info { | ||
89 | const char* name; | ||
90 | const char* description; | ||
91 | jz_device_type device_type; | ||
92 | jz_cpu_type cpu_type; | ||
93 | uint16_t vendor_id; | ||
94 | uint16_t product_id; | ||
95 | int num_actions; | ||
96 | const char* const* action_names; | ||
97 | const jz_device_action_fn* action_funcs; | ||
98 | const char* const* const* action_params; | ||
99 | }; | ||
100 | |||
101 | struct jz_buffer { | ||
102 | size_t size; | ||
103 | uint8_t* data; | ||
104 | }; | ||
105 | |||
106 | /****************************************************************************** | ||
107 | * Library context and general functions | ||
108 | */ | ||
109 | |||
110 | jz_context* jz_context_create(void); | ||
111 | void jz_context_destroy(jz_context* jz); | ||
112 | |||
113 | void jz_context_set_user_data(jz_context* jz, void* ptr); | ||
114 | void* jz_context_get_user_data(jz_context* jz); | ||
115 | |||
116 | void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb); | ||
117 | void jz_context_set_log_level(jz_context* jz, jz_log_level lev); | ||
118 | |||
119 | void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...); | ||
120 | void jz_log_cb_stderr(jz_log_level lev, const char* msg); | ||
121 | |||
122 | void jz_sleepms(int ms); | ||
123 | |||
124 | /****************************************************************************** | ||
125 | * Device and file info | ||
126 | */ | ||
127 | |||
128 | int jz_get_num_device_info(void); | ||
129 | const jz_device_info* jz_get_device_info(jz_device_type type); | ||
130 | const jz_device_info* jz_get_device_info_named(const char* name); | ||
131 | const jz_device_info* jz_get_device_info_indexed(int index); | ||
132 | |||
133 | int jz_identify_x1000_spl(const void* data, size_t len); | ||
134 | int jz_identify_scramble_image(const void* data, size_t len); | ||
135 | int jz_identify_fiiom3k_bootimage(const void* data, size_t len); | ||
136 | |||
137 | /****************************************************************************** | ||
138 | * USB boot ROM protocol | ||
139 | */ | ||
140 | |||
141 | int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id); | ||
142 | void jz_usb_close(jz_usbdev* dev); | ||
143 | |||
144 | int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data); | ||
145 | int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data); | ||
146 | int jz_usb_start1(jz_usbdev* dev, uint32_t addr); | ||
147 | int jz_usb_start2(jz_usbdev* dev, uint32_t addr); | ||
148 | int jz_usb_flush_caches(jz_usbdev* dev); | ||
149 | |||
150 | /****************************************************************************** | ||
151 | * X1000 flash protocol | ||
152 | */ | ||
153 | |||
154 | int jz_x1000_setup(jz_usbdev* dev, size_t spl_len, const void* spl_data); | ||
155 | int jz_x1000_read_flash(jz_usbdev* dev, uint32_t addr, size_t len, void* data); | ||
156 | int jz_x1000_write_flash(jz_usbdev* dev, uint32_t addr, size_t len, const void* data); | ||
157 | int jz_x1000_boot_rockbox(jz_usbdev* dev); | ||
158 | |||
159 | /****************************************************************************** | ||
160 | * FiiO M3K bootloader backup/installation | ||
161 | */ | ||
162 | |||
163 | int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr); | ||
164 | int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf); | ||
165 | int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size, | ||
166 | const void* spl_buf, size_t spl_size, | ||
167 | const void* boot_buf, size_t boot_size); | ||
168 | |||
169 | int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl); | ||
170 | int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl); | ||
171 | int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl); | ||
172 | |||
173 | /****************************************************************************** | ||
174 | * Simple buffer API | ||
175 | */ | ||
176 | |||
177 | jz_buffer* jz_buffer_alloc(size_t size, const void* data); | ||
178 | void jz_buffer_free(jz_buffer* buf); | ||
179 | |||
180 | int jz_buffer_load(jz_buffer** buf, const char* filename); | ||
181 | int jz_buffer_save(jz_buffer* buf, const char* filename); | ||
182 | |||
183 | /****************************************************************************** | ||
184 | * Parameter list | ||
185 | */ | ||
186 | |||
187 | jz_paramlist* jz_paramlist_new(void); | ||
188 | void jz_paramlist_free(jz_paramlist* pl); | ||
189 | int jz_paramlist_set(jz_paramlist* pl, const char* param, const char* value); | ||
190 | const char* jz_paramlist_get(jz_paramlist* pl, const char* param); | ||
191 | |||
192 | /****************************************************************************** | ||
193 | * END | ||
194 | */ | ||
195 | |||
196 | #ifdef __cplusplus | ||
197 | } | ||
198 | #endif | ||
199 | |||
200 | #endif /* JZTOOL_H */ | ||
diff --git a/rbutil/jztool/jztool.c b/rbutil/jztool/jztool.c new file mode 100644 index 0000000000..5aae8d7457 --- /dev/null +++ b/rbutil/jztool/jztool.c | |||
@@ -0,0 +1,227 @@ | |||
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 | const jz_device_info* dev_info = NULL; | ||
30 | int dev_action = -1; | ||
31 | jz_paramlist* action_params = NULL; | ||
32 | |||
33 | void usage(void) | ||
34 | { | ||
35 | printf("Usage:\n" | ||
36 | " jztool [global options] <device> <action> [action options]\n" | ||
37 | "\n" | ||
38 | "Global options:\n" | ||
39 | "\n" | ||
40 | " -h, --help Display this help\n" | ||
41 | " -q, --quiet Don't log anything except errors\n" | ||
42 | " -v, --verbose Display detailed logging output\n" | ||
43 | " -l, --loglevel LEVEL Set log level\n"); | ||
44 | |||
45 | printf("Supported devices:\n\n"); | ||
46 | int n = jz_get_num_device_info(); | ||
47 | for(int i = 0; i < n; ++i) { | ||
48 | const jz_device_info* info = jz_get_device_info_indexed(i); | ||
49 | printf(" %s - %s\n", info->name, info->description); | ||
50 | } | ||
51 | |||
52 | printf( | ||
53 | "\n" | ||
54 | "Available actions for fiiom3k:\n" | ||
55 | "\n" | ||
56 | " install --spl <spl.m3k> --bootloader <bootloader.m3k>\n" | ||
57 | " [--without-backup yes] [--backup IMAGE]\n" | ||
58 | " Install or update the Rockbox bootloader on a device.\n" | ||
59 | "\n" | ||
60 | " If --backup is given, back up the current bootloader to IMAGE before\n" | ||
61 | " installing the new bootloader. The installer will normally refuse to\n" | ||
62 | " overwrite your current bootloader; pass '--without-backup yes' if you\n" | ||
63 | " really want to proceed without taking a backup.\n" | ||
64 | "\n" | ||
65 | " WARNING: it is NOT RECOMMENDED to install the Rockbox bootloader\n" | ||
66 | " without taking a backup of the original firmware bootloader. It may\n" | ||
67 | " be very difficult or impossible to recover your player without one.\n" | ||
68 | " At least one M3Ks is known to not to work with the Rockbox bootloader,\n" | ||
69 | " so it is very important to take a backup.\n" | ||
70 | "\n" | ||
71 | " backup --image IMAGE\n" | ||
72 | " Backup the current bootloader to the file IMAGE\n" | ||
73 | "\n" | ||
74 | " restore --image IMAGE\n" | ||
75 | " Restore a bootloader image backup from the file IMAGE\n" | ||
76 | "\n"); | ||
77 | |||
78 | exit(4); | ||
79 | } | ||
80 | |||
81 | void cleanup(void) | ||
82 | { | ||
83 | if(action_params) | ||
84 | jz_paramlist_free(action_params); | ||
85 | if(jz) | ||
86 | jz_context_destroy(jz); | ||
87 | } | ||
88 | |||
89 | int main(int argc, char** argv) | ||
90 | { | ||
91 | if(argc < 2) | ||
92 | usage(); | ||
93 | |||
94 | /* Library initialization */ | ||
95 | jz = jz_context_create(); | ||
96 | if(!jz) { | ||
97 | fprintf(stderr, "ERROR: Can't create context"); | ||
98 | return 1; | ||
99 | } | ||
100 | |||
101 | atexit(cleanup); | ||
102 | jz_context_set_log_cb(jz, jz_log_cb_stderr); | ||
103 | jz_context_set_log_level(jz, JZ_LOG_NOTICE); | ||
104 | |||
105 | /* Parse global options */ | ||
106 | --argc, ++argv; | ||
107 | while(argc > 0 && argv[0][0] == '-') { | ||
108 | if(!strcmp(*argv, "-h") || !strcmp(*argv, "--help")) | ||
109 | usage(); | ||
110 | else if(!strcmp(*argv, "-q") || !strcmp(*argv, "--quiet")) | ||
111 | jz_context_set_log_level(jz, JZ_LOG_ERROR); | ||
112 | else if(!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose")) | ||
113 | jz_context_set_log_level(jz, JZ_LOG_DETAIL); | ||
114 | else if(!strcmp(*argv, "-l") || !strcmp(*argv, "--loglevel")) { | ||
115 | ++argv; | ||
116 | if(--argc == 0) { | ||
117 | jz_log(jz, JZ_LOG_ERROR, "Missing argument to option %s", *argv); | ||
118 | exit(2); | ||
119 | } | ||
120 | |||
121 | enum jz_log_level level; | ||
122 | if(!strcmp(*argv, "ignore")) | ||
123 | level = JZ_LOG_IGNORE; | ||
124 | else if(!strcmp(*argv, "error")) | ||
125 | level = JZ_LOG_ERROR; | ||
126 | else if(!strcmp(*argv, "warning")) | ||
127 | level = JZ_LOG_WARNING; | ||
128 | else if(!strcmp(*argv, "notice")) | ||
129 | level = JZ_LOG_NOTICE; | ||
130 | else if(!strcmp(*argv, "detail")) | ||
131 | level = JZ_LOG_DETAIL; | ||
132 | else if(!strcmp(*argv, "debug")) | ||
133 | level = JZ_LOG_DEBUG; | ||
134 | else { | ||
135 | jz_log(jz, JZ_LOG_ERROR, "Invalid log level '%s'", *argv); | ||
136 | exit(2); | ||
137 | } | ||
138 | |||
139 | jz_context_set_log_level(jz, level); | ||
140 | } else { | ||
141 | jz_log(jz, JZ_LOG_ERROR, "Invalid global option '%s'", *argv); | ||
142 | exit(2); | ||
143 | } | ||
144 | |||
145 | --argc, ++argv; | ||
146 | } | ||
147 | |||
148 | /* Read the device type */ | ||
149 | if(argc == 0) { | ||
150 | jz_log(jz, JZ_LOG_ERROR, "No device specified (try jztool --help)"); | ||
151 | exit(2); | ||
152 | } | ||
153 | |||
154 | dev_info = jz_get_device_info_named(*argv); | ||
155 | if(!dev_info) { | ||
156 | jz_log(jz, JZ_LOG_ERROR, "Unknown device '%s' (try jztool --help)", *argv); | ||
157 | exit(2); | ||
158 | } | ||
159 | |||
160 | /* Read the action */ | ||
161 | --argc, ++argv; | ||
162 | if(argc == 0) { | ||
163 | jz_log(jz, JZ_LOG_ERROR, "No action specified (try jztool --help)"); | ||
164 | exit(2); | ||
165 | } | ||
166 | |||
167 | for(dev_action = 0; dev_action < dev_info->num_actions; ++dev_action) | ||
168 | if(!strcmp(*argv, dev_info->action_names[dev_action])) | ||
169 | break; | ||
170 | |||
171 | if(dev_action == dev_info->num_actions) { | ||
172 | jz_log(jz, JZ_LOG_ERROR, "Unknown action '%s' (try jztool --help)", *argv); | ||
173 | exit(2); | ||
174 | } | ||
175 | |||
176 | /* Parse the action options */ | ||
177 | action_params = jz_paramlist_new(); | ||
178 | if(!action_params) { | ||
179 | jz_log(jz, JZ_LOG_ERROR, "Out of memory: can't create paramlist"); | ||
180 | exit(1); | ||
181 | } | ||
182 | |||
183 | const char* const* allowed_params = dev_info->action_params[dev_action]; | ||
184 | |||
185 | --argc, ++argv; | ||
186 | while(argc > 0 && argv[0][0] == '-') { | ||
187 | if(argv[0][1] != '-') { | ||
188 | jz_log(jz, JZ_LOG_ERROR, "Invalid option '%s' for action", *argv); | ||
189 | exit(2); | ||
190 | } | ||
191 | |||
192 | bool bad_option = true; | ||
193 | for(int i = 0; allowed_params[i] != NULL; ++i) { | ||
194 | if(!strcmp(&argv[0][2], allowed_params[i])) { | ||
195 | ++argv; | ||
196 | if(--argc == 0) { | ||
197 | jz_log(jz, JZ_LOG_ERROR, "Missing argument for parameter '%s'", *argv); | ||
198 | exit(2); | ||
199 | } | ||
200 | |||
201 | int rc = jz_paramlist_set(action_params, allowed_params[i], *argv); | ||
202 | if(rc < 0) { | ||
203 | jz_log(jz, JZ_LOG_ERROR, "Out of memory"); | ||
204 | exit(1); | ||
205 | } | ||
206 | |||
207 | bad_option = false; | ||
208 | } | ||
209 | } | ||
210 | |||
211 | if(bad_option) { | ||
212 | jz_log(jz, JZ_LOG_ERROR, "Invalid option '%s' for action", *argv); | ||
213 | exit(2); | ||
214 | } | ||
215 | |||
216 | --argc, ++argv; | ||
217 | } | ||
218 | |||
219 | if(argc != 0) { | ||
220 | jz_log(jz, JZ_LOG_ERROR, "Excess arguments on command line"); | ||
221 | exit(2); | ||
222 | } | ||
223 | |||
224 | /* Invoke action handler */ | ||
225 | int rc = dev_info->action_funcs[dev_action](jz, action_params); | ||
226 | return (rc < 0) ? 1 : 0; | ||
227 | } | ||
diff --git a/rbutil/jztool/src/buffer.c b/rbutil/jztool/src/buffer.c new file mode 100644 index 0000000000..9e9c9ff5d1 --- /dev/null +++ b/rbutil/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/rbutil/jztool/src/context.c b/rbutil/jztool/src/context.c new file mode 100644 index 0000000000..94b21b5196 --- /dev/null +++ b/rbutil/jztool/src/context.c | |||
@@ -0,0 +1,167 @@ | |||
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 | /** \brief Allocate a library context | ||
31 | * \returns New context or NULL if out of memory | ||
32 | */ | ||
33 | jz_context* jz_context_create(void) | ||
34 | { | ||
35 | jz_context* jz = malloc(sizeof(struct jz_context)); | ||
36 | if(!jz) | ||
37 | return NULL; | ||
38 | |||
39 | memset(jz, 0, sizeof(struct jz_context)); | ||
40 | jz->log_level = JZ_LOG_ERROR; | ||
41 | return jz; | ||
42 | } | ||
43 | |||
44 | /** \brief Destroy the context and free its memory */ | ||
45 | void jz_context_destroy(jz_context* jz) | ||
46 | { | ||
47 | if(jz->usb_ctx) { | ||
48 | jz_log(jz, JZ_LOG_ERROR, "BUG: USB was not cleaned up properly"); | ||
49 | libusb_exit(jz->usb_ctx); | ||
50 | } | ||
51 | |||
52 | free(jz); | ||
53 | } | ||
54 | |||
55 | /** \brief Set a user data pointer. Useful for callbacks. */ | ||
56 | void jz_context_set_user_data(jz_context* jz, void* ptr) | ||
57 | { | ||
58 | jz->user_data = ptr; | ||
59 | } | ||
60 | |||
61 | /** \brief Get the user data pointer */ | ||
62 | void* jz_context_get_user_data(jz_context* jz) | ||
63 | { | ||
64 | return jz->user_data; | ||
65 | } | ||
66 | |||
67 | /** \brief Set the log message callback. | ||
68 | * \note By default, no message callback is set! No messages will be logged | ||
69 | * in this case, so ensure you set a callback if messages are desired. | ||
70 | */ | ||
71 | void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb) | ||
72 | { | ||
73 | jz->log_cb = cb; | ||
74 | } | ||
75 | |||
76 | /** \brief Set the log level. | ||
77 | * | ||
78 | * Messages of less importance than the set log level are not logged. | ||
79 | * The default log level is `JZ_LOG_WARNING`. The special log level | ||
80 | * `JZ_LOG_IGNORE` can be used to disable all logging temporarily. | ||
81 | * | ||
82 | * The `JZ_LOG_DEBUG` log level is extremely verbose and will log all calls, | ||
83 | * normally it's only useful for catching bugs. | ||
84 | */ | ||
85 | void jz_context_set_log_level(jz_context* jz, jz_log_level lev) | ||
86 | { | ||
87 | jz->log_level = lev; | ||
88 | } | ||
89 | |||
90 | /** \brief Log an informational message. | ||
91 | * \param lev Log level for this message | ||
92 | * \param fmt `printf` style message format string | ||
93 | */ | ||
94 | void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...) | ||
95 | { | ||
96 | if(!jz->log_cb) | ||
97 | return; | ||
98 | if(lev == JZ_LOG_IGNORE) | ||
99 | return; | ||
100 | if(lev > jz->log_level) | ||
101 | return; | ||
102 | |||
103 | va_list ap; | ||
104 | |||
105 | va_start(ap, fmt); | ||
106 | int n = vsnprintf(NULL, 0, fmt, ap); | ||
107 | va_end(ap); | ||
108 | |||
109 | if(n < 0) | ||
110 | return; | ||
111 | |||
112 | char* buf = malloc(n + 1); | ||
113 | if(!buf) | ||
114 | return; | ||
115 | |||
116 | va_start(ap, fmt); | ||
117 | n = vsnprintf(buf, n + 1, fmt, ap); | ||
118 | va_end(ap); | ||
119 | |||
120 | if(n >= 0) | ||
121 | jz->log_cb(lev, buf); | ||
122 | |||
123 | free(buf); | ||
124 | } | ||
125 | |||
126 | /** \brief Log callback which writes messages to `stderr`. | ||
127 | */ | ||
128 | void jz_log_cb_stderr(jz_log_level lev, const char* msg) | ||
129 | { | ||
130 | static const char* const tags[] = | ||
131 | {"ERROR", "WARNING", "NOTICE", "DETAIL", "DEBUG"}; | ||
132 | fprintf(stderr, "[%7s] %s\n", tags[lev], msg); | ||
133 | fflush(stderr); | ||
134 | } | ||
135 | |||
136 | /** \brief Sleep for `ms` milliseconds. | ||
137 | */ | ||
138 | void jz_sleepms(int ms) | ||
139 | { | ||
140 | struct timespec ts; | ||
141 | long ns = ms % 1000; | ||
142 | ts.tv_nsec = ns * 1000 * 1000; | ||
143 | ts.tv_sec = ms / 1000; | ||
144 | nanosleep(&ts, NULL); | ||
145 | } | ||
146 | |||
147 | int jz_context_ref_libusb(jz_context* jz) | ||
148 | { | ||
149 | if(jz->usb_ctxref == 0) { | ||
150 | int rc = libusb_init(&jz->usb_ctx); | ||
151 | if(rc < 0) { | ||
152 | jz_log(jz, JZ_LOG_ERROR, "libusb_init: %s", libusb_strerror(rc)); | ||
153 | return JZ_ERR_USB; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | jz->usb_ctxref += 1; | ||
158 | return JZ_SUCCESS; | ||
159 | } | ||
160 | |||
161 | void jz_context_unref_libusb(jz_context* jz) | ||
162 | { | ||
163 | if(--jz->usb_ctxref == 0) { | ||
164 | libusb_exit(jz->usb_ctx); | ||
165 | jz->usb_ctx = NULL; | ||
166 | } | ||
167 | } | ||
diff --git a/rbutil/jztool/src/device_info.c b/rbutil/jztool/src/device_info.c new file mode 100644 index 0000000000..bc1477be32 --- /dev/null +++ b/rbutil/jztool/src/device_info.c | |||
@@ -0,0 +1,98 @@ | |||
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 char* const fiiom3k_action_names[] = { | ||
26 | "install", | ||
27 | "backup", | ||
28 | "restore", | ||
29 | }; | ||
30 | |||
31 | static const char* const fiiom3k_install_action_params[] = | ||
32 | {"spl", "bootloader", "backup", "without-backup", NULL}; | ||
33 | |||
34 | static const char* const fiiom3k_backuprestore_action_params[] = | ||
35 | {"spl", "image", NULL}; | ||
36 | |||
37 | static const char* const* fiiom3k_action_params[] = { | ||
38 | fiiom3k_install_action_params, | ||
39 | fiiom3k_backuprestore_action_params, | ||
40 | fiiom3k_backuprestore_action_params, | ||
41 | }; | ||
42 | |||
43 | static const jz_device_action_fn fiiom3k_action_funcs[] = { | ||
44 | jz_fiiom3k_install, | ||
45 | jz_fiiom3k_backup, | ||
46 | jz_fiiom3k_restore, | ||
47 | }; | ||
48 | |||
49 | static const jz_device_info infotable[] = { | ||
50 | { | ||
51 | .name = "fiiom3k", | ||
52 | .description = "FiiO M3K", | ||
53 | .device_type = JZ_DEVICE_FIIOM3K, | ||
54 | .cpu_type = JZ_CPU_X1000, | ||
55 | .vendor_id = 0xa108, | ||
56 | .product_id = 0x1000, | ||
57 | .num_actions = sizeof(fiiom3k_action_names)/sizeof(void*), | ||
58 | .action_names = fiiom3k_action_names, | ||
59 | .action_funcs = fiiom3k_action_funcs, | ||
60 | .action_params = fiiom3k_action_params, | ||
61 | }, | ||
62 | }; | ||
63 | |||
64 | static const int infotable_size = sizeof(infotable)/sizeof(struct jz_device_info); | ||
65 | |||
66 | /** \brief Get the number of entries in the device info list */ | ||
67 | int jz_get_num_device_info(void) | ||
68 | { | ||
69 | return infotable_size; | ||
70 | } | ||
71 | |||
72 | const jz_device_info* jz_get_device_info(jz_device_type type) | ||
73 | { | ||
74 | for(int i = 0; i < infotable_size; ++i) | ||
75 | if(infotable[i].device_type == type) | ||
76 | return &infotable[i]; | ||
77 | |||
78 | return NULL; | ||
79 | } | ||
80 | |||
81 | /** \brief Lookup info for a device by name, returns NULL if not found. */ | ||
82 | const jz_device_info* jz_get_device_info_named(const char* name) | ||
83 | { | ||
84 | for(int i = 0; i < infotable_size; ++i) | ||
85 | if(!strcmp(infotable[i].name, name)) | ||
86 | return &infotable[i]; | ||
87 | |||
88 | return NULL; | ||
89 | } | ||
90 | |||
91 | /** \brief Get a device info entry by index, returns NULL if out of range. */ | ||
92 | const jz_device_info* jz_get_device_info_indexed(int index) | ||
93 | { | ||
94 | if(index < infotable_size) | ||
95 | return &infotable[index]; | ||
96 | else | ||
97 | return NULL; | ||
98 | } | ||
diff --git a/rbutil/jztool/src/fiiom3k.c b/rbutil/jztool/src/fiiom3k.c new file mode 100644 index 0000000000..a43863c2f7 --- /dev/null +++ b/rbutil/jztool/src/fiiom3k.c | |||
@@ -0,0 +1,283 @@ | |||
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 | #define IMAGE_ADDR 0 | ||
26 | #define IMAGE_SIZE (128 * 1024) | ||
27 | #define SPL_OFFSET 0 | ||
28 | #define SPL_SIZE (12 * 1024) | ||
29 | #define BOOT_OFFSET (26 * 1024) | ||
30 | #define BOOT_SIZE (102 * 1024) | ||
31 | |||
32 | int jz_fiiom3k_readboot(jz_usbdev* dev, jz_buffer** bufptr) | ||
33 | { | ||
34 | jz_buffer* buf = jz_buffer_alloc(IMAGE_SIZE, NULL); | ||
35 | if(!buf) | ||
36 | return JZ_ERR_OUT_OF_MEMORY; | ||
37 | |||
38 | int rc = jz_x1000_read_flash(dev, IMAGE_ADDR, buf->size, buf->data); | ||
39 | if(rc < 0) { | ||
40 | jz_buffer_free(buf); | ||
41 | return rc; | ||
42 | } | ||
43 | |||
44 | *bufptr = buf; | ||
45 | return JZ_SUCCESS; | ||
46 | } | ||
47 | |||
48 | int jz_fiiom3k_writeboot(jz_usbdev* dev, size_t image_size, const void* image_buf) | ||
49 | { | ||
50 | int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size); | ||
51 | if(rc < 0 || image_size != IMAGE_SIZE) | ||
52 | return JZ_ERR_BAD_FILE_FORMAT; | ||
53 | |||
54 | rc = jz_x1000_write_flash(dev, IMAGE_ADDR, image_size, image_buf); | ||
55 | if(rc < 0) | ||
56 | return rc; | ||
57 | |||
58 | return JZ_SUCCESS; | ||
59 | } | ||
60 | |||
61 | int jz_fiiom3k_patchboot(jz_context* jz, void* image_buf, size_t image_size, | ||
62 | const void* spl_buf, size_t spl_size, | ||
63 | const void* boot_buf, size_t boot_size) | ||
64 | { | ||
65 | int rc = jz_identify_fiiom3k_bootimage(image_buf, image_size); | ||
66 | if(rc < 0) { | ||
67 | jz_log(jz, JZ_LOG_ERROR, "Boot image is invalid: %d", rc); | ||
68 | return JZ_ERR_BAD_FILE_FORMAT; | ||
69 | } | ||
70 | |||
71 | rc = jz_identify_x1000_spl(spl_buf, spl_size); | ||
72 | if(rc < 0) { | ||
73 | jz_log(jz, JZ_LOG_ERROR, "SPL image is invalid: %d", rc); | ||
74 | return JZ_ERR_BAD_FILE_FORMAT; | ||
75 | } | ||
76 | |||
77 | if(spl_size > SPL_SIZE) { | ||
78 | jz_log(jz, JZ_LOG_ERROR, "SPL is too big"); | ||
79 | return JZ_ERR_BAD_FILE_FORMAT; | ||
80 | } | ||
81 | |||
82 | rc = jz_identify_scramble_image(boot_buf, boot_size); | ||
83 | if(rc < 0) { | ||
84 | jz_log(jz, JZ_LOG_ERROR, "Bootloader image is invalid: %d", rc); | ||
85 | return JZ_ERR_BAD_FILE_FORMAT; | ||
86 | } | ||
87 | |||
88 | if(boot_size > BOOT_SIZE) { | ||
89 | jz_log(jz, JZ_LOG_ERROR, "Bootloader is too big"); | ||
90 | return JZ_ERR_BAD_FILE_FORMAT; | ||
91 | } | ||
92 | |||
93 | uint8_t* imgdat = (uint8_t*)image_buf; | ||
94 | memset(&imgdat[SPL_OFFSET], 0xff, SPL_SIZE); | ||
95 | memcpy(&imgdat[SPL_OFFSET], spl_buf, spl_size); | ||
96 | memset(&imgdat[BOOT_OFFSET], 0xff, BOOT_SIZE); | ||
97 | memcpy(&imgdat[BOOT_OFFSET], boot_buf, boot_size); | ||
98 | return JZ_SUCCESS; | ||
99 | } | ||
100 | |||
101 | #define IMGBUF 0 | ||
102 | #define SPLBUF 1 | ||
103 | #define BOOTBUF 2 | ||
104 | #define NUMBUFS 3 | ||
105 | #define IMGBUF_NAME "image" | ||
106 | #define SPLBUF_NAME "spl" | ||
107 | #define BOOTBUF_NAME "bootloader" | ||
108 | #define FIIOM3K_INIT_WORKSTATE {0} | ||
109 | |||
110 | struct fiiom3k_workstate { | ||
111 | jz_usbdev* dev; | ||
112 | jz_buffer* bufs[NUMBUFS]; | ||
113 | }; | ||
114 | |||
115 | static void fiiom3k_action_cleanup(struct fiiom3k_workstate* state) | ||
116 | { | ||
117 | for(int i = 0; i < NUMBUFS; ++i) | ||
118 | if(state->bufs[i]) | ||
119 | jz_buffer_free(state->bufs[i]); | ||
120 | |||
121 | if(state->dev) | ||
122 | jz_usb_close(state->dev); | ||
123 | } | ||
124 | |||
125 | static int fiiom3k_action_loadbuf(jz_context* jz, jz_paramlist* pl, | ||
126 | struct fiiom3k_workstate* state, int idx) | ||
127 | { | ||
128 | const char* const paramnames[] = {IMGBUF_NAME, SPLBUF_NAME, BOOTBUF_NAME}; | ||
129 | |||
130 | if(state->bufs[idx]) | ||
131 | return JZ_SUCCESS; | ||
132 | |||
133 | const char* filename = jz_paramlist_get(pl, paramnames[idx]); | ||
134 | if(!filename) { | ||
135 | jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", paramnames[idx]); | ||
136 | return JZ_ERR_OTHER; | ||
137 | } | ||
138 | |||
139 | int rc = jz_buffer_load(&state->bufs[idx], filename); | ||
140 | if(rc < 0) { | ||
141 | jz_log(jz, JZ_LOG_ERROR, "Error reading '%s' file (%d): %s", paramnames[idx], rc, filename); | ||
142 | return rc; | ||
143 | } | ||
144 | |||
145 | return JZ_SUCCESS; | ||
146 | } | ||
147 | |||
148 | static int fiiom3k_action_setup(jz_context* jz, jz_paramlist* pl, | ||
149 | struct fiiom3k_workstate* state) | ||
150 | { | ||
151 | const jz_device_info* info = jz_get_device_info(JZ_DEVICE_FIIOM3K); | ||
152 | if(!info) | ||
153 | return JZ_ERR_OTHER; | ||
154 | |||
155 | int rc = fiiom3k_action_loadbuf(jz, pl, state, SPLBUF); | ||
156 | if(rc < 0) | ||
157 | return rc; | ||
158 | |||
159 | jz_log(jz, JZ_LOG_DETAIL, "Open USB device %04x:%04x", | ||
160 | (unsigned int)info->vendor_id, (unsigned int)info->product_id); | ||
161 | rc = jz_usb_open(jz, &state->dev, info->vendor_id, info->product_id); | ||
162 | if(rc < 0) | ||
163 | return rc; | ||
164 | |||
165 | jz_log(jz, JZ_LOG_DETAIL, "Setup device for flash access"); | ||
166 | jz_buffer* splbuf = state->bufs[SPLBUF]; | ||
167 | return jz_x1000_setup(state->dev, splbuf->size, splbuf->data); | ||
168 | } | ||
169 | |||
170 | int jz_fiiom3k_install(jz_context* jz, jz_paramlist* pl) | ||
171 | { | ||
172 | struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; | ||
173 | int rc; | ||
174 | |||
175 | rc = fiiom3k_action_loadbuf(jz, pl, &state, BOOTBUF); | ||
176 | if(rc < 0) | ||
177 | goto error; | ||
178 | |||
179 | rc = fiiom3k_action_setup(jz, pl, &state); | ||
180 | if(rc < 0) | ||
181 | goto error; | ||
182 | |||
183 | jz_log(jz, JZ_LOG_DETAIL, "Reading boot image from device"); | ||
184 | rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]); | ||
185 | if(rc < 0) | ||
186 | goto error; | ||
187 | |||
188 | jz_buffer* img_buf = state.bufs[IMGBUF]; | ||
189 | const char* backupfile = jz_paramlist_get(pl, "backup"); | ||
190 | const char* without_backup = jz_paramlist_get(pl, "without-backup"); | ||
191 | if(backupfile) { | ||
192 | jz_log(jz, JZ_LOG_DETAIL, "Backup original boot image to file: %s", backupfile); | ||
193 | rc = jz_buffer_save(img_buf, backupfile); | ||
194 | if(rc < 0) { | ||
195 | jz_log(jz, JZ_LOG_ERROR, "Error saving backup image file (%d): %s", rc, backupfile); | ||
196 | goto error; | ||
197 | } | ||
198 | } else if(!without_backup || strcmp(without_backup, "yes")) { | ||
199 | jz_log(jz, JZ_LOG_ERROR, "No --backup option given and --without-backup yes not specified"); | ||
200 | jz_log(jz, JZ_LOG_ERROR, "Refusing to flash a new bootloader without taking a backup"); | ||
201 | goto error; | ||
202 | } | ||
203 | |||
204 | jz_log(jz, JZ_LOG_DETAIL, "Patching image with new SPL/bootloader"); | ||
205 | jz_buffer* boot_buf = state.bufs[BOOTBUF]; | ||
206 | jz_buffer* spl_buf = state.bufs[SPLBUF]; | ||
207 | rc = jz_fiiom3k_patchboot(jz, img_buf->data, img_buf->size, | ||
208 | spl_buf->data, spl_buf->size, | ||
209 | boot_buf->data, boot_buf->size); | ||
210 | if(rc < 0) { | ||
211 | jz_log(jz, JZ_LOG_ERROR, "Error patching image: %d", rc); | ||
212 | goto error; | ||
213 | } | ||
214 | |||
215 | jz_log(jz, JZ_LOG_DETAIL, "Writing patched image to device"); | ||
216 | rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data); | ||
217 | if(rc < 0) | ||
218 | goto error; | ||
219 | |||
220 | rc = JZ_SUCCESS; | ||
221 | |||
222 | error: | ||
223 | fiiom3k_action_cleanup(&state); | ||
224 | return rc; | ||
225 | } | ||
226 | |||
227 | int jz_fiiom3k_backup(jz_context* jz, jz_paramlist* pl) | ||
228 | { | ||
229 | struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; | ||
230 | int rc; | ||
231 | |||
232 | const char* outfile_path = jz_paramlist_get(pl, IMGBUF_NAME); | ||
233 | if(!outfile_path) { | ||
234 | jz_log(jz, JZ_LOG_ERROR, "Missing required parameter '%s'", IMGBUF_NAME); | ||
235 | rc = JZ_ERR_OTHER; | ||
236 | goto error; | ||
237 | } | ||
238 | |||
239 | rc = fiiom3k_action_setup(jz, pl, &state); | ||
240 | if(rc < 0) | ||
241 | goto error; | ||
242 | |||
243 | rc = jz_fiiom3k_readboot(state.dev, &state.bufs[IMGBUF]); | ||
244 | if(rc < 0) | ||
245 | goto error; | ||
246 | |||
247 | rc = jz_buffer_save(state.bufs[IMGBUF], outfile_path); | ||
248 | if(rc < 0) { | ||
249 | jz_log(jz, JZ_LOG_ERROR, "Error writing '%s' file (%d): %s", IMGBUF_NAME, rc, outfile_path); | ||
250 | goto error; | ||
251 | } | ||
252 | |||
253 | rc = JZ_SUCCESS; | ||
254 | |||
255 | error: | ||
256 | fiiom3k_action_cleanup(&state); | ||
257 | return rc; | ||
258 | } | ||
259 | |||
260 | int jz_fiiom3k_restore(jz_context* jz, jz_paramlist* pl) | ||
261 | { | ||
262 | struct fiiom3k_workstate state = FIIOM3K_INIT_WORKSTATE; | ||
263 | int rc; | ||
264 | |||
265 | rc = fiiom3k_action_loadbuf(jz, pl, &state, IMGBUF); | ||
266 | if(rc < 0) | ||
267 | goto error; | ||
268 | |||
269 | rc = fiiom3k_action_setup(jz, pl, &state); | ||
270 | if(rc < 0) | ||
271 | goto error; | ||
272 | |||
273 | jz_buffer* img_buf = state.bufs[IMGBUF]; | ||
274 | rc = jz_fiiom3k_writeboot(state.dev, img_buf->size, img_buf->data); | ||
275 | if(rc < 0) | ||
276 | goto error; | ||
277 | |||
278 | rc = JZ_SUCCESS; | ||
279 | |||
280 | error: | ||
281 | fiiom3k_action_cleanup(&state); | ||
282 | return rc; | ||
283 | } | ||
diff --git a/rbutil/jztool/src/identify_file.c b/rbutil/jztool/src/identify_file.c new file mode 100644 index 0000000000..3bf4a9ced7 --- /dev/null +++ b/rbutil/jztool/src/identify_file.c | |||
@@ -0,0 +1,179 @@ | |||
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 | int jz_identify_x1000_spl(const void* data, size_t len) | ||
85 | { | ||
86 | /* Use <= check because a header-only file is not really valid, | ||
87 | * it should have at least one byte in it... */ | ||
88 | if(len <= X1000_SPL_HEADER_SIZE) | ||
89 | return JZ_IDERR_WRONG_SIZE; | ||
90 | |||
91 | /* Look for header magic bytes */ | ||
92 | const struct x1000_spl_header* header = (const struct x1000_spl_header*)data; | ||
93 | if(memcmp(header->magic, x1000_spl_header_magic, 8)) | ||
94 | return JZ_IDERR_BAD_HEADER; | ||
95 | |||
96 | /* Length stored in the header should equal the length of the file */ | ||
97 | if(header->length != len) | ||
98 | return JZ_IDERR_WRONG_SIZE; | ||
99 | |||
100 | /* Compute the CRC7 checksum; it only covers the SPL code */ | ||
101 | const uint8_t* dat = (const uint8_t*)data; | ||
102 | uint8_t sum = crc7(&dat[X1000_SPL_HEADER_SIZE], len - X1000_SPL_HEADER_SIZE); | ||
103 | if(header->crc7 != sum) | ||
104 | return JZ_IDERR_BAD_CHECKSUM; | ||
105 | |||
106 | return JZ_SUCCESS; | ||
107 | |||
108 | } | ||
109 | |||
110 | static const struct scramble_model_info { | ||
111 | const char* name; | ||
112 | int model_num; | ||
113 | } scramble_models[] = { | ||
114 | {"fiio", 114}, | ||
115 | {NULL, 0}, | ||
116 | }; | ||
117 | |||
118 | int jz_identify_scramble_image(const void* data, size_t len) | ||
119 | { | ||
120 | /* 4 bytes checksum + 4 bytes player model */ | ||
121 | if(len < 8) | ||
122 | return JZ_IDERR_WRONG_SIZE; | ||
123 | |||
124 | /* Look up the model number */ | ||
125 | const uint8_t* dat = (const uint8_t*)data; | ||
126 | const struct scramble_model_info* model_info = &scramble_models[0]; | ||
127 | for(; model_info->name != NULL; ++model_info) | ||
128 | if(!memcmp(&dat[4], model_info->name, 4)) | ||
129 | break; | ||
130 | |||
131 | if(model_info->name == NULL) | ||
132 | return JZ_IDERR_UNRECOGNIZED_MODEL; | ||
133 | |||
134 | /* Compute the checksum */ | ||
135 | uint32_t sum = model_info->model_num; | ||
136 | for(size_t i = 8; i < len; ++i) | ||
137 | sum += dat[i]; | ||
138 | |||
139 | /* Compare with file's checksum, it's stored in big-endian form */ | ||
140 | uint32_t fsum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3]; | ||
141 | if(sum != fsum) | ||
142 | return JZ_IDERR_BAD_CHECKSUM; | ||
143 | |||
144 | return JZ_SUCCESS; | ||
145 | } | ||
146 | |||
147 | int jz_identify_fiiom3k_bootimage(const void* data, size_t len) | ||
148 | { | ||
149 | /* The bootloader image is simply a dump of the first NAND eraseblock, | ||
150 | * so it has a fixed 128 KiB size */ | ||
151 | if(len != 128*1024) | ||
152 | return JZ_IDERR_WRONG_SIZE; | ||
153 | |||
154 | /* We'll verify the embedded SPL, but we have to drag out the correct | ||
155 | * length from the header. Length should be more than 12 KiB, due to | ||
156 | * limitations of the hardware */ | ||
157 | const struct x1000_spl_header* spl_header; | ||
158 | spl_header = (const struct x1000_spl_header*)data; | ||
159 | if(spl_header->length > 12 * 1024) | ||
160 | return JZ_IDERR_BAD_HEADER; | ||
161 | |||
162 | int rc = jz_identify_x1000_spl(data, spl_header->length); | ||
163 | if(rc < 0) | ||
164 | return rc; | ||
165 | |||
166 | const uint8_t* dat = (const uint8_t*)data; | ||
167 | |||
168 | /* Check the partition table is present */ | ||
169 | if(memcmp(&dat[0x3c00], "nand", 4)) | ||
170 | return JZ_IDERR_OTHER; | ||
171 | |||
172 | /* Check first bytes of PDMA firmware. It doesn't change | ||
173 | * between OF versions, and Rockbox doesn't modify it. */ | ||
174 | static const uint8_t pdma_fw[] = {0x54, 0x25, 0x42, 0xb3, 0x70, 0x25, 0x42, 0xb3}; | ||
175 | if(memcmp(&dat[0x4000], pdma_fw, sizeof(pdma_fw))) | ||
176 | return JZ_IDERR_OTHER; | ||
177 | |||
178 | return JZ_SUCCESS; | ||
179 | } | ||
diff --git a/rbutil/jztool/src/jztool_private.h b/rbutil/jztool/src/jztool_private.h new file mode 100644 index 0000000000..11299f21f9 --- /dev/null +++ b/rbutil/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/rbutil/jztool/src/paramlist.c b/rbutil/jztool/src/paramlist.c new file mode 100644 index 0000000000..05bcf97a13 --- /dev/null +++ b/rbutil/jztool/src/paramlist.c | |||
@@ -0,0 +1,135 @@ | |||
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 <stdbool.h> | ||
26 | |||
27 | struct jz_paramlist { | ||
28 | int size; | ||
29 | char** keys; | ||
30 | char** values; | ||
31 | }; | ||
32 | |||
33 | static int jz_paramlist_extend(jz_paramlist* pl, int count) | ||
34 | { | ||
35 | int nsize = pl->size + count; | ||
36 | |||
37 | /* Reallocate key list */ | ||
38 | char** nkeys = realloc(pl->keys, nsize * sizeof(char*)); | ||
39 | if(!nkeys) | ||
40 | return JZ_ERR_OUT_OF_MEMORY; | ||
41 | |||
42 | for(int i = pl->size; i < nsize; ++i) | ||
43 | nkeys[i] = NULL; | ||
44 | |||
45 | pl->keys = nkeys; | ||
46 | |||
47 | /* Reallocate value list */ | ||
48 | char** nvalues = realloc(pl->values, nsize * sizeof(char*)); | ||
49 | if(!nvalues) | ||
50 | return JZ_ERR_OUT_OF_MEMORY; | ||
51 | |||
52 | for(int i = pl->size; i < nsize; ++i) | ||
53 | nvalues[i] = NULL; | ||
54 | |||
55 | pl->values = nvalues; | ||
56 | |||
57 | pl->size = nsize; | ||
58 | return JZ_SUCCESS; | ||
59 | } | ||
60 | |||
61 | jz_paramlist* jz_paramlist_new(void) | ||
62 | { | ||
63 | jz_paramlist* pl = malloc(sizeof(struct jz_paramlist)); | ||
64 | if(!pl) | ||
65 | return NULL; | ||
66 | |||
67 | pl->size = 0; | ||
68 | pl->keys = NULL; | ||
69 | pl->values = NULL; | ||
70 | return pl; | ||
71 | } | ||
72 | |||
73 | void jz_paramlist_free(jz_paramlist* pl) | ||
74 | { | ||
75 | for(int i = 0; i < pl->size; ++i) { | ||
76 | free(pl->keys[i]); | ||
77 | free(pl->values[i]); | ||
78 | } | ||
79 | |||
80 | if(pl->size > 0) { | ||
81 | free(pl->keys); | ||
82 | free(pl->values); | ||
83 | } | ||
84 | |||
85 | free(pl); | ||
86 | } | ||
87 | |||
88 | int jz_paramlist_set(jz_paramlist* pl, const char* param, const char* value) | ||
89 | { | ||
90 | int pos = -1; | ||
91 | for(int i = 0; i < pl->size; ++i) { | ||
92 | if(!pl->keys[i] || !strcmp(pl->keys[i], param)) { | ||
93 | pos = i; | ||
94 | break; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | if(pos == -1) { | ||
99 | pos = pl->size; | ||
100 | int rc = jz_paramlist_extend(pl, 1); | ||
101 | if(rc < 0) | ||
102 | return rc; | ||
103 | } | ||
104 | |||
105 | bool need_key = (pl->keys[pos] == NULL); | ||
106 | if(need_key) { | ||
107 | char* newparam = strdup(param); | ||
108 | if(!newparam) | ||
109 | return JZ_ERR_OUT_OF_MEMORY; | ||
110 | |||
111 | pl->keys[pos] = newparam; | ||
112 | } | ||
113 | |||
114 | char* newvalue = strdup(value); | ||
115 | if(!newvalue) { | ||
116 | if(need_key) { | ||
117 | free(pl->keys[pos]); | ||
118 | pl->keys[pos] = NULL; | ||
119 | } | ||
120 | |||
121 | return JZ_ERR_OUT_OF_MEMORY; | ||
122 | } | ||
123 | |||
124 | pl->values[pos] = newvalue; | ||
125 | return JZ_SUCCESS; | ||
126 | } | ||
127 | |||
128 | const char* jz_paramlist_get(jz_paramlist* pl, const char* param) | ||
129 | { | ||
130 | for(int i = 0; i < pl->size; ++i) | ||
131 | if(pl->keys[i] && !strcmp(pl->keys[i], param)) | ||
132 | return pl->values[i]; | ||
133 | |||
134 | return NULL; | ||
135 | } | ||
diff --git a/rbutil/jztool/src/usb.c b/rbutil/jztool/src/usb.c new file mode 100644 index 0000000000..7e4a5f3388 --- /dev/null +++ b/rbutil/jztool/src/usb.c | |||
@@ -0,0 +1,203 @@ | |||
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 | |||
26 | #define VR_GET_CPU_INFO 0 | ||
27 | #define VR_SET_DATA_ADDRESS 1 | ||
28 | #define VR_SET_DATA_LENGTH 2 | ||
29 | #define VR_FLUSH_CACHES 3 | ||
30 | #define VR_PROGRAM_START1 4 | ||
31 | #define VR_PROGRAM_START2 5 | ||
32 | |||
33 | int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id) | ||
34 | { | ||
35 | int rc; | ||
36 | jz_usbdev* dev = NULL; | ||
37 | libusb_device_handle* usb_handle = NULL; | ||
38 | libusb_device** dev_list = NULL; | ||
39 | ssize_t dev_index = -1, dev_count; | ||
40 | |||
41 | rc = jz_context_ref_libusb(jz); | ||
42 | if(rc < 0) | ||
43 | return rc; | ||
44 | |||
45 | dev = malloc(sizeof(struct jz_usbdev)); | ||
46 | if(!dev) { | ||
47 | rc = JZ_ERR_OUT_OF_MEMORY; | ||
48 | goto error; | ||
49 | } | ||
50 | |||
51 | dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list); | ||
52 | if(dev_count < 0) { | ||
53 | jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count)); | ||
54 | rc = JZ_ERR_USB; | ||
55 | goto error; | ||
56 | } | ||
57 | |||
58 | for(ssize_t i = 0; i < dev_count; ++i) { | ||
59 | struct libusb_device_descriptor desc; | ||
60 | rc = libusb_get_device_descriptor(dev_list[i], &desc); | ||
61 | if(rc < 0) { | ||
62 | jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s", | ||
63 | libusb_strerror(rc)); | ||
64 | continue; | ||
65 | } | ||
66 | |||
67 | if(desc.idVendor != vend_id || desc.idProduct != prod_id) | ||
68 | continue; | ||
69 | |||
70 | if(dev_index >= 0) { | ||
71 | /* not the best, but it is the safest thing */ | ||
72 | jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x", | ||
73 | (unsigned int)vend_id, (unsigned int)prod_id); | ||
74 | jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again"); | ||
75 | rc = JZ_ERR_NO_DEVICE; | ||
76 | goto error; | ||
77 | } | ||
78 | |||
79 | dev_index = i; | ||
80 | } | ||
81 | |||
82 | if(dev_index < 0) { | ||
83 | jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%05x found", | ||
84 | (unsigned int)vend_id, (unsigned int)prod_id); | ||
85 | rc = JZ_ERR_NO_DEVICE; | ||
86 | goto error; | ||
87 | } | ||
88 | |||
89 | rc = libusb_open(dev_list[dev_index], &usb_handle); | ||
90 | if(rc < 0) { | ||
91 | jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc)); | ||
92 | rc = JZ_ERR_USB; | ||
93 | goto error; | ||
94 | } | ||
95 | |||
96 | rc = libusb_claim_interface(usb_handle, 0); | ||
97 | if(rc < 0) { | ||
98 | jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc)); | ||
99 | rc = JZ_ERR_USB; | ||
100 | goto error; | ||
101 | } | ||
102 | |||
103 | dev->jz = jz; | ||
104 | dev->handle = usb_handle; | ||
105 | *devptr = dev; | ||
106 | rc = JZ_SUCCESS; | ||
107 | |||
108 | exit: | ||
109 | if(dev_list) | ||
110 | libusb_free_device_list(dev_list, true); | ||
111 | return rc; | ||
112 | |||
113 | error: | ||
114 | if(dev) | ||
115 | free(dev); | ||
116 | if(usb_handle) | ||
117 | libusb_close(usb_handle); | ||
118 | jz_context_unref_libusb(jz); | ||
119 | goto exit; | ||
120 | } | ||
121 | |||
122 | void jz_usb_close(jz_usbdev* dev) | ||
123 | { | ||
124 | libusb_release_interface(dev->handle, 0); | ||
125 | libusb_close(dev->handle); | ||
126 | jz_context_unref_libusb(dev->jz); | ||
127 | free(dev); | ||
128 | } | ||
129 | |||
130 | static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg) | ||
131 | { | ||
132 | int rc = libusb_control_transfer(dev->handle, | ||
133 | LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, | ||
134 | req, arg >> 16, arg & 0xffff, NULL, 0, 1000); | ||
135 | |||
136 | if(rc < 0) { | ||
137 | jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc)); | ||
138 | rc = JZ_ERR_USB; | ||
139 | } else { | ||
140 | rc = JZ_SUCCESS; | ||
141 | } | ||
142 | |||
143 | return rc; | ||
144 | } | ||
145 | |||
146 | static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf) | ||
147 | { | ||
148 | int xfered = 0; | ||
149 | int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1; | ||
150 | int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000); | ||
151 | |||
152 | if(rc < 0) { | ||
153 | jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc)); | ||
154 | rc = JZ_ERR_USB; | ||
155 | } else if(xfered != (int)len) { | ||
156 | jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered"); | ||
157 | rc = JZ_ERR_USB; | ||
158 | } else { | ||
159 | rc = JZ_SUCCESS; | ||
160 | } | ||
161 | |||
162 | return rc; | ||
163 | } | ||
164 | |||
165 | static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr, | ||
166 | size_t len, void* data) | ||
167 | { | ||
168 | int rc; | ||
169 | rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr); | ||
170 | if(rc < 0) | ||
171 | return rc; | ||
172 | |||
173 | rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len); | ||
174 | if(rc < 0) | ||
175 | return rc; | ||
176 | |||
177 | return jz_usb_transfer(dev, write, len, data); | ||
178 | } | ||
179 | |||
180 | int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data) | ||
181 | { | ||
182 | return jz_usb_sendrecv(dev, true, addr, len, (void*)data); | ||
183 | } | ||
184 | |||
185 | int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data) | ||
186 | { | ||
187 | return jz_usb_sendrecv(dev, false, addr, len, data); | ||
188 | } | ||
189 | |||
190 | int jz_usb_start1(jz_usbdev* dev, uint32_t addr) | ||
191 | { | ||
192 | return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr); | ||
193 | } | ||
194 | |||
195 | int jz_usb_start2(jz_usbdev* dev, uint32_t addr) | ||
196 | { | ||
197 | return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr); | ||
198 | } | ||
199 | |||
200 | int jz_usb_flush_caches(jz_usbdev* dev) | ||
201 | { | ||
202 | return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0); | ||
203 | } | ||
diff --git a/rbutil/jztool/src/x1000.c b/rbutil/jztool/src/x1000.c new file mode 100644 index 0000000000..049344e5e6 --- /dev/null +++ b/rbutil/jztool/src/x1000.c | |||
@@ -0,0 +1,193 @@ | |||
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 "../../../firmware/target/mips/ingenic_x1000/spl-x1000-defs.h" | ||
24 | #include "../../../firmware/target/mips/ingenic_x1000/nand-x1000-err.h" | ||
25 | #include <endian.h> // TODO: portability | ||
26 | #include <string.h> | ||
27 | |||
28 | static const char* jz_x1000_nand_strerror(int rc) | ||
29 | { | ||
30 | switch(rc) { | ||
31 | case NANDERR_CHIP_UNSUPPORTED: | ||
32 | return "Chip unsupported"; | ||
33 | case NANDERR_WRITE_PROTECTED: | ||
34 | return "Operation forbidden by write-protect"; | ||
35 | case NANDERR_UNALIGNED_ADDRESS: | ||
36 | return "Improperly aligned address"; | ||
37 | case NANDERR_UNALIGNED_LENGTH: | ||
38 | return "Improperly aligned length"; | ||
39 | case NANDERR_READ_FAILED: | ||
40 | return "Read operation failed"; | ||
41 | case NANDERR_ECC_FAILED: | ||
42 | return "Uncorrectable ECC error on read"; | ||
43 | case NANDERR_ERASE_FAILED: | ||
44 | return "Erase operation failed"; | ||
45 | case NANDERR_PROGRAM_FAILED: | ||
46 | return "Program operation failed"; | ||
47 | case NANDERR_COMMAND_FAILED: | ||
48 | return "NAND command failed"; | ||
49 | default: | ||
50 | return "Unknown NAND error"; | ||
51 | } | ||
52 | } | ||
53 | |||
54 | |||
55 | static int jz_x1000_send_args(jz_usbdev* dev, struct x1000_spl_arguments* args) | ||
56 | { | ||
57 | args->command = htole32(args->command); | ||
58 | args->param1 = htole32(args->param1); | ||
59 | args->param2 = htole32(args->param2); | ||
60 | args->flags = htole32(args->flags); | ||
61 | return jz_usb_send(dev, SPL_ARGUMENTS_ADDRESS, sizeof(*args), args); | ||
62 | } | ||
63 | |||
64 | static int jz_x1000_recv_status(jz_usbdev* dev, struct x1000_spl_status* status) | ||
65 | { | ||
66 | int rc = jz_usb_recv(dev, SPL_STATUS_ADDRESS, sizeof(*status), status); | ||
67 | if(rc < 0) | ||
68 | return rc; | ||
69 | |||
70 | status->err_code = le32toh(status->err_code); | ||
71 | status->reserved = le32toh(status->reserved); | ||
72 | return JZ_SUCCESS; | ||
73 | } | ||
74 | |||
75 | int jz_x1000_setup(jz_usbdev* dev, size_t spl_len, const void* spl_data) | ||
76 | { | ||
77 | int rc = jz_identify_x1000_spl(spl_data, spl_len); | ||
78 | if(rc < 0) | ||
79 | return JZ_ERR_BAD_FILE_FORMAT; | ||
80 | if(spl_len > SPL_MAX_SIZE) | ||
81 | return JZ_ERR_BAD_FILE_FORMAT; | ||
82 | |||
83 | rc = jz_usb_send(dev, SPL_LOAD_ADDRESS, spl_len, spl_data); | ||
84 | if(rc < 0) | ||
85 | return rc; | ||
86 | |||
87 | struct x1000_spl_arguments args; | ||
88 | args.command = SPL_CMD_BOOT; | ||
89 | args.param1 = SPL_BOOTOPT_NONE; | ||
90 | args.param2 = 0; | ||
91 | args.flags = 0; | ||
92 | rc = jz_x1000_send_args(dev, &args); | ||
93 | if(rc < 0) | ||
94 | return rc; | ||
95 | |||
96 | rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS); | ||
97 | if(rc < 0) | ||
98 | return rc; | ||
99 | |||
100 | jz_sleepms(100); | ||
101 | |||
102 | struct x1000_spl_status status; | ||
103 | rc = jz_x1000_recv_status(dev, &status); | ||
104 | if(rc < 0) | ||
105 | return rc; | ||
106 | |||
107 | if(status.err_code != 0) { | ||
108 | jz_log(dev->jz, JZ_LOG_ERROR, "X1000 device init error: %d", status.err_code); | ||
109 | return JZ_ERR_OTHER; | ||
110 | } | ||
111 | |||
112 | return JZ_SUCCESS; | ||
113 | } | ||
114 | |||
115 | int jz_x1000_read_flash(jz_usbdev* dev, uint32_t addr, size_t len, void* data) | ||
116 | { | ||
117 | struct x1000_spl_arguments args; | ||
118 | args.command = SPL_CMD_FLASH_READ; | ||
119 | args.param1 = addr; | ||
120 | args.param2 = len; | ||
121 | args.flags = SPL_FLAG_SKIP_INIT; | ||
122 | int rc = jz_x1000_send_args(dev, &args); | ||
123 | if(rc < 0) | ||
124 | return rc; | ||
125 | |||
126 | rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS); | ||
127 | if(rc < 0) | ||
128 | return rc; | ||
129 | |||
130 | jz_sleepms(500); | ||
131 | |||
132 | struct x1000_spl_status status; | ||
133 | rc = jz_x1000_recv_status(dev, &status); | ||
134 | if(rc < 0) | ||
135 | return rc; | ||
136 | |||
137 | if(status.err_code != 0) { | ||
138 | jz_log(dev->jz, JZ_LOG_ERROR, "X1000 flash read error: %s", | ||
139 | jz_x1000_nand_strerror(status.err_code)); | ||
140 | return JZ_ERR_FLASH_ERROR; | ||
141 | } | ||
142 | |||
143 | return jz_usb_recv(dev, SPL_BUFFER_ADDRESS, len, data); | ||
144 | } | ||
145 | |||
146 | int jz_x1000_write_flash(jz_usbdev* dev, uint32_t addr, size_t len, const void* data) | ||
147 | { | ||
148 | int rc = jz_usb_send(dev, SPL_BUFFER_ADDRESS, len, data); | ||
149 | if(rc < 0) | ||
150 | return rc; | ||
151 | |||
152 | struct x1000_spl_arguments args; | ||
153 | args.command = SPL_CMD_FLASH_WRITE; | ||
154 | args.param1 = addr; | ||
155 | args.param2 = len; | ||
156 | args.flags = SPL_FLAG_SKIP_INIT; | ||
157 | rc = jz_x1000_send_args(dev, &args); | ||
158 | if(rc < 0) | ||
159 | return rc; | ||
160 | |||
161 | rc = jz_usb_start1(dev, SPL_EXEC_ADDRESS); | ||
162 | if(rc < 0) | ||
163 | return rc; | ||
164 | |||
165 | jz_sleepms(500); | ||
166 | |||
167 | struct x1000_spl_status status; | ||
168 | rc = jz_x1000_recv_status(dev, &status); | ||
169 | if(rc < 0) | ||
170 | return rc; | ||
171 | |||
172 | if(status.err_code != 0) { | ||
173 | jz_log(dev->jz, JZ_LOG_ERROR, "X1000 flash write error: %s", | ||
174 | jz_x1000_nand_strerror(status.err_code)); | ||
175 | return JZ_ERR_FLASH_ERROR; | ||
176 | } | ||
177 | |||
178 | return JZ_SUCCESS; | ||
179 | } | ||
180 | |||
181 | int jz_x1000_boot_rockbox(jz_usbdev* dev) | ||
182 | { | ||
183 | struct x1000_spl_arguments args; | ||
184 | args.command = SPL_CMD_BOOT; | ||
185 | args.param1 = SPL_BOOTOPT_ROCKBOX; | ||
186 | args.param2 = 0; | ||
187 | args.flags = 0; | ||
188 | int rc = jz_x1000_send_args(dev, &args); | ||
189 | if(rc < 0) | ||
190 | return rc; | ||
191 | |||
192 | return jz_usb_start1(dev, SPL_EXEC_ADDRESS); | ||
193 | } | ||