summaryrefslogtreecommitdiff
path: root/utils/jztool/src
diff options
context:
space:
mode:
Diffstat (limited to 'utils/jztool/src')
-rw-r--r--utils/jztool/src/buffer.c134
-rw-r--r--utils/jztool/src/context.c177
-rw-r--r--utils/jztool/src/device_info.c109
-rw-r--r--utils/jztool/src/identify_file.c170
-rw-r--r--utils/jztool/src/jztool_private.h44
-rw-r--r--utils/jztool/src/ucl_unpack.c128
-rw-r--r--utils/jztool/src/usb.c291
-rw-r--r--utils/jztool/src/x1000.c180
8 files changed, 1233 insertions, 0 deletions
diff --git a/utils/jztool/src/buffer.c b/utils/jztool/src/buffer.c
new file mode 100644
index 0000000000..9e9c9ff5d1
--- /dev/null
+++ b/utils/jztool/src/buffer.c
@@ -0,0 +1,134 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <stdlib.h>
24#include <string.h>
25#include <stdio.h>
26
27/** \brief Allocate a buffer, optionally providing its contents.
28 * \param size Number of bytes to allocate
29 * \param data Initial contents of the buffer, must be at least `size` bytes
30 * \return Pointer to buffer or NULL if out of memory.
31 * \note The buffer will not take ownership of the `data` pointer, instead it
32 * allocates a fresh buffer and copies the contents of `data` into it.
33 */
34jz_buffer* jz_buffer_alloc(size_t size, const void* data)
35{
36 jz_buffer* buf = malloc(sizeof(struct jz_buffer));
37 if(!buf)
38 return NULL;
39
40 buf->data = malloc(size);
41 if(!buf->data) {
42 free(buf);
43 return NULL;
44 }
45
46 if(data)
47 memcpy(buf->data, data, size);
48
49 buf->size = size;
50 return buf;
51}
52
53/** \brief Free a buffer
54 */
55void jz_buffer_free(jz_buffer* buf)
56{
57 if(buf) {
58 free(buf->data);
59 free(buf);
60 }
61}
62
63/** \brief Load a buffer from a file
64 * \param buf Returns loaded buffer on success, unmodified on error
65 * \param filename Path to the file
66 * \return either JZ_SUCCESS, or one of the following errors
67 * \retval JZ_ERR_OPEN_FILE file cannot be opened
68 * \retval JZ_ERR_OUT_OF_MEMORY cannot allocate buffer to hold file contents
69 * \retval JZ_ERR_FILE_IO problem reading file data
70 */
71int jz_buffer_load(jz_buffer** buf, const char* filename)
72{
73 FILE* f;
74 jz_buffer* b;
75 int rc;
76
77 f = fopen(filename, "rb");
78 if(!f)
79 return JZ_ERR_OPEN_FILE;
80
81 fseek(f, 0, SEEK_END);
82 int size = ftell(f);
83 fseek(f, 0, SEEK_SET);
84
85 b = jz_buffer_alloc(size, NULL);
86 if(!b) {
87 rc = JZ_ERR_OUT_OF_MEMORY;
88 goto err_fclose;
89 }
90
91 if(fread(b->data, size, 1, f) != 1) {
92 rc = JZ_ERR_FILE_IO;
93 goto err_free_buf;
94 }
95
96 rc = JZ_SUCCESS;
97 *buf = b;
98
99 err_fclose:
100 fclose(f);
101 return rc;
102
103 err_free_buf:
104 jz_buffer_free(b);
105 goto err_fclose;
106}
107
108/** \brief Save a buffer to a file
109 * \param buf Buffer to be written out
110 * \param filename Path to the file
111 * \return either JZ_SUCCESS, or one of the following errors
112 * \retval JZ_ERR_OPEN_FILE file cannot be opened
113 * \retval JZ_ERR_FILE_IO problem writing file data
114 */
115int jz_buffer_save(jz_buffer* buf, const char* filename)
116{
117 int rc;
118 FILE* f;
119
120 f = fopen(filename, "wb");
121 if(!f)
122 return JZ_ERR_OPEN_FILE;
123
124 if(fwrite(buf->data, buf->size, 1, f) != 1) {
125 rc = JZ_ERR_FILE_IO;
126 goto err_fclose;
127 }
128
129 rc = JZ_SUCCESS;
130
131 err_fclose:
132 fclose(f);
133 return rc;
134}
diff --git a/utils/jztool/src/context.c b/utils/jztool/src/context.c
new file mode 100644
index 0000000000..d269d1eece
--- /dev/null
+++ b/utils/jztool/src/context.c
@@ -0,0 +1,177 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool_private.h"
23#include <string.h>
24#include <stdlib.h>
25#include <stddef.h>
26#include <stdarg.h>
27#include <stdio.h>
28#include <time.h>
29
30#ifdef WIN32
31# include <windows.h>
32#endif
33
34/** \brief Allocate a library context
35 * \returns New context or NULL if out of memory
36 */
37jz_context* jz_context_create(void)
38{
39 jz_context* jz = malloc(sizeof(struct jz_context));
40 if(!jz)
41 return NULL;
42
43 memset(jz, 0, sizeof(struct jz_context));
44 jz->log_level = JZ_LOG_ERROR;
45 return jz;
46}
47
48/** \brief Destroy the context and free its memory */
49void jz_context_destroy(jz_context* jz)
50{
51 if(jz->usb_ctx) {
52 jz_log(jz, JZ_LOG_ERROR, "BUG: USB was not cleaned up properly");
53 libusb_exit(jz->usb_ctx);
54 }
55
56 free(jz);
57}
58
59/** \brief Set a user data pointer. Useful for callbacks. */
60void jz_context_set_user_data(jz_context* jz, void* ptr)
61{
62 jz->user_data = ptr;
63}
64
65/** \brief Get the user data pointer */
66void* jz_context_get_user_data(jz_context* jz)
67{
68 return jz->user_data;
69}
70
71/** \brief Set the log message callback.
72 * \note By default, no message callback is set! No messages will be logged
73 * in this case, so ensure you set a callback if messages are desired.
74 */
75void jz_context_set_log_cb(jz_context* jz, jz_log_cb cb)
76{
77 jz->log_cb = cb;
78}
79
80/** \brief Set the log level.
81 *
82 * Messages of less importance than the set log level are not logged.
83 * The default log level is `JZ_LOG_WARNING`. The special log level
84 * `JZ_LOG_IGNORE` can be used to disable all logging temporarily.
85 *
86 * The `JZ_LOG_DEBUG` log level is extremely verbose and will log all calls,
87 * normally it's only useful for catching bugs.
88 */
89void jz_context_set_log_level(jz_context* jz, jz_log_level lev)
90{
91 jz->log_level = lev;
92}
93
94/** \brief Log an informational message.
95 * \param lev Log level for this message
96 * \param fmt `printf` style message format string
97 */
98void jz_log(jz_context* jz, jz_log_level lev, const char* fmt, ...)
99{
100 if(!jz->log_cb)
101 return;
102 if(lev == JZ_LOG_IGNORE)
103 return;
104 if(lev > jz->log_level)
105 return;
106
107 va_list ap;
108
109 va_start(ap, fmt);
110 int n = vsnprintf(NULL, 0, fmt, ap);
111 va_end(ap);
112
113 if(n < 0)
114 return;
115
116 char* buf = malloc(n + 1);
117 if(!buf)
118 return;
119
120 va_start(ap, fmt);
121 n = vsnprintf(buf, n + 1, fmt, ap);
122 va_end(ap);
123
124 if(n >= 0)
125 jz->log_cb(lev, buf);
126
127 free(buf);
128}
129
130/** \brief Log callback which writes messages to `stderr`.
131 */
132void jz_log_cb_stderr(jz_log_level lev, const char* msg)
133{
134 static const char* const tags[] =
135 {"ERROR", "WARNING", "NOTICE", "DETAIL", "DEBUG"};
136 fprintf(stderr, "[%7s] %s\n", tags[lev], msg);
137 fflush(stderr);
138}
139
140/** \brief Sleep for `ms` milliseconds.
141 */
142void jz_sleepms(int ms)
143{
144#ifdef WIN32
145 Sleep(ms);
146#else
147 struct timespec ts;
148 long ns = ms % 1000;
149 ts.tv_nsec = ns * 1000 * 1000;
150 ts.tv_sec = ms / 1000;
151 nanosleep(&ts, NULL);
152#endif
153}
154
155/** \brief Add reference to libusb context, allocating it if necessary */
156int jz_context_ref_libusb(jz_context* jz)
157{
158 if(jz->usb_ctxref == 0) {
159 int rc = libusb_init(&jz->usb_ctx);
160 if(rc < 0) {
161 jz_log(jz, JZ_LOG_ERROR, "libusb_init: %s", libusb_strerror(rc));
162 return JZ_ERR_USB;
163 }
164 }
165
166 jz->usb_ctxref += 1;
167 return JZ_SUCCESS;
168}
169
170/** \brief Remove reference to libusb context, freeing if it hits zero */
171void jz_context_unref_libusb(jz_context* jz)
172{
173 if(--jz->usb_ctxref == 0) {
174 libusb_exit(jz->usb_ctx);
175 jz->usb_ctx = NULL;
176 }
177}
diff --git a/utils/jztool/src/device_info.c b/utils/jztool/src/device_info.c
new file mode 100644
index 0000000000..cc431959ca
--- /dev/null
+++ b/utils/jztool/src/device_info.c
@@ -0,0 +1,109 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <string.h>
24
25static const jz_device_info infotable[JZ_NUM_DEVICES] = {
26 [JZ_DEVICE_FIIOM3K] = {
27 .name = "fiiom3k",
28 .file_ext = "m3k",
29 .description = "FiiO M3K",
30 .device_type = JZ_DEVICE_FIIOM3K,
31 .cpu_type = JZ_CPU_X1000,
32 .vendor_id = 0x2972,
33 .product_id = 0x0003,
34 },
35 [JZ_DEVICE_SHANLINGQ1] = {
36 .name = "shanlingq1",
37 .file_ext = "q1",
38 .description = "Shanling Q1",
39 .device_type = JZ_DEVICE_SHANLINGQ1,
40 .cpu_type = JZ_CPU_X1000,
41 .vendor_id = 0x0525,
42 .product_id = 0xa4a5,
43 },
44 [JZ_DEVICE_EROSQ] = {
45 .name = "erosq",
46 .file_ext = "erosq",
47 .description = "AIGO Eros Q",
48 .device_type = JZ_DEVICE_EROSQ,
49 .cpu_type = JZ_CPU_X1000,
50 .vendor_id = 0xc502,
51 .product_id = 0x0023,
52 },
53};
54
55static const jz_cpu_info cputable[JZ_NUM_CPUS] = {
56 [JZ_CPU_X1000] = {
57 .info_str = "X1000_v1",
58 .vendor_id = 0xa108,
59 .product_id = 0x1000,
60 .stage1_load_addr = 0xf4001000,
61 .stage1_exec_addr = 0xf4001800,
62 .stage2_load_addr = 0x80004000,
63 .stage2_exec_addr = 0x80004000,
64 },
65};
66
67/** \brief Lookup info for a device by type, returns NULL if not found. */
68const jz_device_info* jz_get_device_info(jz_device_type type)
69{
70 return jz_get_device_info_indexed(type);
71}
72
73/** \brief Lookup info for a device by name, returns NULL if not found. */
74const jz_device_info* jz_get_device_info_named(const char* name)
75{
76 for(int i = 0; i < JZ_NUM_DEVICES; ++i)
77 if(!strcmp(infotable[i].name, name))
78 return &infotable[i];
79
80 return NULL;
81}
82
83/** \brief Get a device info entry by index, returns NULL if out of range. */
84const jz_device_info* jz_get_device_info_indexed(int index)
85{
86 if(index < JZ_NUM_DEVICES)
87 return &infotable[index];
88 else
89 return NULL;
90}
91
92/** \brief Lookup info for a CPU, returns NULL if not found. */
93const jz_cpu_info* jz_get_cpu_info(jz_cpu_type type)
94{
95 if(type < JZ_NUM_CPUS)
96 return &cputable[type];
97 else
98 return NULL;
99}
100
101/** \brief Lookup info for a CPU by info string, returns NULL if not found. */
102const jz_cpu_info* jz_get_cpu_info_named(const char* info_str)
103{
104 for(int i = 0; i < JZ_NUM_CPUS; ++i)
105 if(!strcmp(cputable[i].info_str, info_str))
106 return &cputable[i];
107
108 return NULL;
109}
diff --git a/utils/jztool/src/identify_file.c b/utils/jztool/src/identify_file.c
new file mode 100644
index 0000000000..e475d98a3b
--- /dev/null
+++ b/utils/jztool/src/identify_file.c
@@ -0,0 +1,170 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include <string.h>
24
25/* Following is copied from mkspl-x1000, basically */
26struct x1000_spl_header {
27 uint8_t magic[8];
28 uint8_t type;
29 uint8_t crc7;
30 uint8_t ppb;
31 uint8_t bpp;
32 uint32_t length;
33};
34
35static const uint8_t x1000_spl_header_magic[8] =
36 {0x06, 0x05, 0x04, 0x03, 0x02, 0x55, 0xaa, 0x55};
37
38static const size_t X1000_SPL_HEADER_SIZE = 2 * 1024;
39
40static uint8_t crc7(const uint8_t* buf, size_t len)
41{
42 /* table-based computation of CRC7 */
43 static const uint8_t t[256] = {
44 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f,
45 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
46 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26,
47 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
48 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d,
49 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
50 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14,
51 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
52 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b,
53 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
54 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42,
55 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
56 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69,
57 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
58 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70,
59 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
60 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e,
61 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
62 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67,
63 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
64 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
65 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
66 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55,
67 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
68 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a,
69 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
70 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03,
71 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
72 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28,
73 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
74 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31,
75 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
76 };
77
78 uint8_t crc = 0;
79 while(len--)
80 crc = t[(crc << 1) ^ *buf++];
81 return crc;
82}
83
84/** \brief Identify a file as an SPL for X1000 CPUs
85 * \param data File data buffer
86 * \param len Length of file
87 * \return JZ_SUCCESS if file looks correct, or one of the following errors
88 * \retval JZ_IDERR_WRONG_SIZE file too small or size doesn't match header
89 * \retval JZ_IDERR_BAD_HEADER missing magic bytes from header
90 * \retval JZ_IDERR_BAD_CHECKSUM CRC7 mismatch
91 */
92int jz_identify_x1000_spl(const void* data, size_t len)
93{
94 /* Use <= check because a header-only file is not really valid,
95 * it should have at least one byte in it... */
96 if(len <= X1000_SPL_HEADER_SIZE)
97 return JZ_IDERR_WRONG_SIZE;
98
99 /* Look for header magic bytes */
100 const struct x1000_spl_header* header = (const struct x1000_spl_header*)data;
101 if(memcmp(header->magic, x1000_spl_header_magic, 8))
102 return JZ_IDERR_BAD_HEADER;
103
104 /* Length stored in the header should equal the length of the file */
105 if(header->length != len)
106 return JZ_IDERR_WRONG_SIZE;
107
108 /* Compute the CRC7 checksum; it only covers the SPL code */
109 const uint8_t* dat = (const uint8_t*)data;
110 uint8_t sum = crc7(&dat[X1000_SPL_HEADER_SIZE], len - X1000_SPL_HEADER_SIZE);
111 if(header->crc7 != sum)
112 return JZ_IDERR_BAD_CHECKSUM;
113
114 return JZ_SUCCESS;
115
116}
117
118static const struct scramble_model_info {
119 const char* name;
120 int model_num;
121 size_t offset_crc;
122 size_t offset_name;
123 size_t offset_data;
124} scramble_models[] = {
125 {"fiio", 114, 0, 4, 8},
126 {"shq1", 115, 0, 4, 8},
127 {"eros", 116, 0, 4, 8},
128 {NULL, 0, 0, 0, 0},
129};
130
131/** \brief Identify a file as a Rockbox `scramble` image
132 * \param data File data buffer
133 * \param len Length of file
134 * \return JZ_SUCCESS if file looks correct, or one of the following errors
135 * \retval JZ_IDERR_UNRECOGNIZED_MODEL unsupported/unknown model type
136 * \retval JZ_IDERR_BAD_CHECKSUM checksum mismatch
137 */
138int jz_identify_scramble_image(const void* data, size_t len)
139{
140 const uint8_t* dat;
141 const struct scramble_model_info* model_info;
142 uint32_t sum, file_sum;
143
144 dat = (const uint8_t*)data;
145 model_info = &scramble_models[0];
146
147 /* Look up the model number */
148 for(; model_info->name != NULL; ++model_info) {
149 if(model_info->offset_name + 4 > len)
150 continue;
151 if(!memcmp(&dat[model_info->offset_name], model_info->name, 4))
152 break;
153 }
154
155 if(model_info->name == NULL)
156 return JZ_IDERR_UNRECOGNIZED_MODEL;
157
158 /* Compute the checksum */
159 sum = model_info->model_num;
160 for(size_t i = model_info->offset_data; i < len; ++i)
161 sum += dat[i];
162
163 /* Compare with file's checksum, it's stored in big-endian form */
164 dat += model_info->offset_crc;
165 file_sum = (dat[0] << 24) | (dat[1] << 16) | (dat[2] << 8) | dat[3];
166 if(sum != file_sum)
167 return JZ_IDERR_BAD_CHECKSUM;
168
169 return JZ_SUCCESS;
170}
diff --git a/utils/jztool/src/jztool_private.h b/utils/jztool/src/jztool_private.h
new file mode 100644
index 0000000000..11299f21f9
--- /dev/null
+++ b/utils/jztool/src/jztool_private.h
@@ -0,0 +1,44 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef JZTOOL_PRIVATE_H
23#define JZTOOL_PRIVATE_H
24
25#include "jztool.h"
26#include <libusb.h>
27
28struct jz_context {
29 void* user_data;
30 jz_log_cb log_cb;
31 jz_log_level log_level;
32 libusb_context* usb_ctx;
33 int usb_ctxref;
34};
35
36struct jz_usbdev {
37 jz_context* jz;
38 libusb_device_handle* handle;
39};
40
41int jz_context_ref_libusb(jz_context* jz);
42void jz_context_unref_libusb(jz_context* jz);
43
44#endif /* JZTOOL_PRIVATE_H */
diff --git a/utils/jztool/src/ucl_unpack.c b/utils/jztool/src/ucl_unpack.c
new file mode 100644
index 0000000000..3b199c7008
--- /dev/null
+++ b/utils/jztool/src/ucl_unpack.c
@@ -0,0 +1,128 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include "ucl/ucl.h"
24
25static uint32_t xread32(const uint8_t* d)
26{
27 uint32_t r = 0;
28 r |= d[0] << 24;
29 r |= d[1] << 16;
30 r |= d[2] << 8;
31 r |= d[3] << 0;
32 return r;
33}
34
35/* adapted from firmware/common/ucl_decompress.c */
36jz_buffer* jz_ucl_unpack(const uint8_t* src, uint32_t src_len, uint32_t* dst_len)
37{
38 static const uint8_t magic[8] =
39 {0x00, 0xe9, 0x55, 0x43, 0x4c, 0xff, 0x01, 0x1a};
40
41 jz_buffer* buffer = NULL;
42
43 /* make sure there are enough bytes for the header */
44 if(src_len < 18)
45 goto error;
46
47 /* avoid memcmp for reasons of code size */
48 for(size_t i = 0; i < sizeof(magic); ++i)
49 if(src[i] != magic[i])
50 goto error;
51
52 /* read the other header fields */
53 /* uint32_t flags = xread32(&src[8]); */
54 uint8_t method = src[12];
55 /* uint8_t level = src[13]; */
56 uint32_t block_size = xread32(&src[14]);
57
58 /* check supported compression method */
59 if(method != 0x2e)
60 goto error;
61
62 /* validate */
63 if(block_size < 1024 || block_size > 8*1024*1024)
64 goto error;
65
66 src += 18;
67 src_len -= 18;
68
69 /* Calculate amount of space that we might need & allocate a buffer:
70 * - subtract 4 to account for end of file marker
71 * - each block is block_size bytes + 8 bytes of header
72 * - add one to nr_blocks to account for case where file size < block size
73 * - total size = max uncompressed size of block * nr_blocks
74 */
75 uint32_t nr_blocks = (src_len - 4) / (8 + block_size) + 1;
76 uint32_t max_size = nr_blocks * (block_size + block_size/8 + 256);
77 buffer = jz_buffer_alloc(max_size, NULL);
78 if(!buffer)
79 goto error;
80
81 /* perform the decompression */
82 uint32_t dst_ilen = buffer->size;
83 uint8_t* dst = buffer->data;
84 while(1) {
85 if(src_len < 4)
86 goto error;
87
88 uint32_t out_len = xread32(src); src += 4, src_len -= 4;
89 if(out_len == 0)
90 break;
91
92 if(src_len < 4)
93 goto error;
94
95 uint32_t in_len = xread32(src); src += 4, src_len -= 4;
96 if(in_len > block_size || out_len > block_size ||
97 in_len == 0 || in_len > out_len)
98 goto error;
99
100 if(src_len < in_len)
101 goto error;
102
103 if(in_len < out_len) {
104 uint32_t actual_out_len = dst_ilen;
105 int rc = ucl_nrv2e_decompress_safe_8(src, in_len, dst, &actual_out_len, NULL);
106 if(rc != UCL_E_OK)
107 goto error;
108 if(actual_out_len != out_len)
109 goto error;
110 } else {
111 for(size_t i = 0; i < in_len; ++i)
112 dst[i] = src[i];
113 }
114
115 src += in_len;
116 src_len -= in_len;
117 dst += out_len;
118 dst_ilen -= out_len;
119 }
120
121 /* subtract leftover number of bytes to get size of compressed output */
122 *dst_len = buffer->size - dst_ilen;
123 return buffer;
124
125 error:
126 jz_buffer_free(buffer);
127 return NULL;
128}
diff --git a/utils/jztool/src/usb.c b/utils/jztool/src/usb.c
new file mode 100644
index 0000000000..cfc3ba60cb
--- /dev/null
+++ b/utils/jztool/src/usb.c
@@ -0,0 +1,291 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool_private.h"
23#include <stdlib.h>
24#include <stdbool.h>
25#include <string.h>
26
27#define VR_GET_CPU_INFO 0
28#define VR_SET_DATA_ADDRESS 1
29#define VR_SET_DATA_LENGTH 2
30#define VR_FLUSH_CACHES 3
31#define VR_PROGRAM_START1 4
32#define VR_PROGRAM_START2 5
33
34/** \brief Open a USB device
35 * \param jz Context
36 * \param devptr Returns pointer to the USB device upon success
37 * \param vend_id USB vendor ID
38 * \param prod_id USB product ID
39 * \return either JZ_SUCCESS if device was opened, or an error below
40 * \retval JZ_ERR_OUT_OF_MEMORY malloc failed
41 * \retval JZ_ERR_USB libusb error (details are logged)
42 * \retval JZ_ERR_NO_DEVICE can't unambiguously find the device
43 */
44int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id)
45{
46 int rc;
47 jz_usbdev* dev = NULL;
48 libusb_device_handle* usb_handle = NULL;
49 libusb_device** dev_list = NULL;
50 ssize_t dev_index = -1, dev_count;
51
52 rc = jz_context_ref_libusb(jz);
53 if(rc < 0)
54 return rc;
55
56 dev = malloc(sizeof(struct jz_usbdev));
57 if(!dev) {
58 rc = JZ_ERR_OUT_OF_MEMORY;
59 goto error;
60 }
61
62 dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list);
63 if(dev_count < 0) {
64 jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count));
65 rc = JZ_ERR_USB;
66 goto error;
67 }
68
69 for(ssize_t i = 0; i < dev_count; ++i) {
70 struct libusb_device_descriptor desc;
71 rc = libusb_get_device_descriptor(dev_list[i], &desc);
72 if(rc < 0) {
73 jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s",
74 libusb_strerror(rc));
75 continue;
76 }
77
78 if(desc.idVendor != vend_id || desc.idProduct != prod_id)
79 continue;
80
81 if(dev_index >= 0) {
82 /* not the best, but it is the safest thing */
83 jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x",
84 (unsigned int)vend_id, (unsigned int)prod_id);
85 jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again");
86 rc = JZ_ERR_NO_DEVICE;
87 goto error;
88 }
89
90 dev_index = i;
91 }
92
93 if(dev_index < 0) {
94 jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%04x found",
95 (unsigned int)vend_id, (unsigned int)prod_id);
96 rc = JZ_ERR_NO_DEVICE;
97 goto error;
98 }
99
100 rc = libusb_open(dev_list[dev_index], &usb_handle);
101 if(rc < 0) {
102 jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc));
103 rc = JZ_ERR_USB;
104 goto error;
105 }
106
107 rc = libusb_claim_interface(usb_handle, 0);
108 if(rc < 0) {
109 jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc));
110 rc = JZ_ERR_USB;
111 goto error;
112 }
113
114 jz_log(jz, JZ_LOG_DEBUG, "Opened device (%p, ID %04x:%04x)",
115 dev, (unsigned int)vend_id, (unsigned int)prod_id);
116 dev->jz = jz;
117 dev->handle = usb_handle;
118 *devptr = dev;
119 rc = JZ_SUCCESS;
120
121 exit:
122 if(dev_list)
123 libusb_free_device_list(dev_list, true);
124 return rc;
125
126 error:
127 if(dev)
128 free(dev);
129 if(usb_handle)
130 libusb_close(usb_handle);
131 jz_context_unref_libusb(jz);
132 goto exit;
133}
134
135/** \brief Close a USB device
136 * \param dev Device to close; memory will be freed automatically
137 */
138void jz_usb_close(jz_usbdev* dev)
139{
140 jz_log(dev->jz, JZ_LOG_DEBUG, "Closing device (%p)", dev);
141 libusb_release_interface(dev->handle, 0);
142 libusb_close(dev->handle);
143 jz_context_unref_libusb(dev->jz);
144 free(dev);
145}
146
147// Does an Ingenic-specific vendor request
148// Written with X1000 in mind but other Ingenic CPUs have the same commands
149static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg,
150 void* buffer, int buflen)
151{
152 int rc = libusb_control_transfer(dev->handle,
153 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
154 req, arg >> 16, arg & 0xffff, buffer, buflen, 1000);
155
156 if(rc < 0) {
157 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc));
158 rc = JZ_ERR_USB;
159 } else {
160 static const char* req_names[] = {
161 "GET_CPU_INFO",
162 "SET_DATA_ADDRESS",
163 "SET_DATA_LENGTH",
164 "FLUSH_CACHES",
165 "PROGRAM_START1",
166 "PROGRAM_START2",
167 };
168
169 jz_log(dev->jz, JZ_LOG_DEBUG, "Issued %s %08lu",
170 req_names[req], (unsigned long)arg);
171 rc = JZ_SUCCESS;
172 }
173
174 return rc;
175}
176
177// Bulk transfer wrapper
178static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf)
179{
180 int xfered = 0;
181 int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1;
182 int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000);
183
184 if(rc < 0) {
185 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc));
186 rc = JZ_ERR_USB;
187 } else if(xfered != (int)len) {
188 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered");
189 rc = JZ_ERR_USB;
190 } else {
191 jz_log(dev->jz, JZ_LOG_DEBUG, "Transferred %zu bytes %s",
192 len, write ? "to device" : "from device");
193 rc = JZ_SUCCESS;
194 }
195
196 return rc;
197}
198
199// Memory send/receive primitive, performs the necessary vendor requests
200// and then tranfers data using the bulk endpoint
201static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr,
202 size_t len, void* data)
203{
204 int rc;
205 rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr, NULL, 0);
206 if(rc < 0)
207 return rc;
208
209 rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0);
210 if(rc < 0)
211 return rc;
212
213 return jz_usb_transfer(dev, write, len, data);
214}
215
216/** \brief Write data to device memory
217 * \param dev USB device
218 * \param addr Address where data should be written
219 * \param len Length of the data, in bytes, should be positive
220 * \param data Data buffer
221 * \return either JZ_SUCCESS on success or a failure code
222 */
223int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data)
224{
225 return jz_usb_sendrecv(dev, true, addr, len, (void*)data);
226}
227
228/** \brief Read data to device memory
229 * \param dev USB device
230 * \param addr Address to read from
231 * \param len Length of the data, in bytes, should be positive
232 * \param data Data buffer
233 * \return either JZ_SUCCESS on success or a failure code
234 */
235int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data)
236{
237 return jz_usb_sendrecv(dev, false, addr, len, data);
238}
239
240/** \brief Execute stage1 program jumping to the specified address
241 * \param dev USB device
242 * \param addr Address to begin execution at
243 * \return either JZ_SUCCESS on success or a failure code
244 */
245int jz_usb_start1(jz_usbdev* dev, uint32_t addr)
246{
247 return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0);
248}
249
250/** \brief Execute stage2 program jumping to the specified address
251 * \param dev USB device
252 * \param addr Address to begin execution at
253 * \return either JZ_SUCCESS on success or a failure code
254 */
255int jz_usb_start2(jz_usbdev* dev, uint32_t addr)
256{
257 return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0);
258}
259
260/** \brief Ask device to flush CPU caches
261 * \param dev USB device
262 * \return either JZ_SUCCESS on success or a failure code
263 */
264int jz_usb_flush_caches(jz_usbdev* dev)
265{
266 return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0);
267}
268
269/** \brief Ask device for CPU info string
270 * \param dev USB device
271 * \param buffer Buffer to hold the info string
272 * \param buflen Size of the buffer, in bytes
273 * \return either JZ_SUCCESS on success or a failure code
274 *
275 * The buffer will always be null terminated, but to ensure the info string is
276 * not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long.
277 */
278int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen)
279{
280 char tmpbuf[JZ_CPUINFO_BUFLEN];
281 int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8);
282 if(rc != JZ_SUCCESS)
283 return rc;
284
285 if(buflen > 0) {
286 strncpy(buffer, tmpbuf, buflen);
287 buffer[buflen - 1] = 0;
288 }
289
290 return JZ_SUCCESS;
291}
diff --git a/utils/jztool/src/x1000.c b/utils/jztool/src/x1000.c
new file mode 100644
index 0000000000..f59727a2ca
--- /dev/null
+++ b/utils/jztool/src/x1000.c
@@ -0,0 +1,180 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2021 Aidan MacDonald
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "jztool.h"
23#include "jztool_private.h"
24#include "microtar-stdio.h"
25#include <stdbool.h>
26#include <string.h>
27
28/* TODO: these functions could be refactored to be CPU-agnostic */
29static int run_stage1(jz_usbdev* dev, jz_buffer* buf)
30{
31 int rc = jz_usb_send(dev, 0xf4001000, buf->size, buf->data);
32 if(rc < 0)
33 return rc;
34
35 return jz_usb_start1(dev, 0xf4001800);
36}
37
38static int run_stage2(jz_usbdev* dev, jz_buffer* buf)
39{
40 int rc = jz_usb_send(dev, 0x80004000, buf->size, buf->data);
41 if(rc < 0)
42 return rc;
43
44 rc = jz_usb_flush_caches(dev);
45 if(rc < 0)
46 return rc;
47
48 return jz_usb_start2(dev, 0x80004000);
49}
50
51static int get_file(jz_context* jz, mtar_t* tar, const char* file,
52 bool decompress, jz_buffer** buf)
53{
54 jz_buffer* buffer = NULL;
55 const mtar_header_t* h;
56 int rc;
57
58 rc = mtar_find(tar, file);
59 if(rc != MTAR_ESUCCESS) {
60 jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc);
61 return JZ_ERR_BAD_FILE_FORMAT;
62 }
63
64 h = mtar_get_header(tar);
65 buffer = jz_buffer_alloc(h->size, NULL);
66 if(!buffer)
67 return JZ_ERR_OUT_OF_MEMORY;
68
69 rc = mtar_read_data(tar, buffer->data, buffer->size);
70 if(rc < 0 || (unsigned)rc != buffer->size) {
71 jz_buffer_free(buffer);
72 jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", file, rc);
73 return JZ_ERR_BAD_FILE_FORMAT;
74 }
75
76 if(decompress) {
77 uint32_t dst_len;
78 jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len);
79 jz_buffer_free(buffer);
80 if(!nbuf) {
81 jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file);
82 return JZ_ERR_BAD_FILE_FORMAT;
83 }
84
85 /* for simplicity just forget original size of buffer */
86 nbuf->size = dst_len;
87 buffer = nbuf;
88 }
89
90 *buf = buffer;
91 return JZ_SUCCESS;
92}
93
94static int show_version(jz_context* jz, jz_buffer* info_file)
95{
96 /* Extract the version string and log it for informational purposes */
97 char* boot_version = (char*)info_file->data;
98 char* endpos = memchr(boot_version, '\n', info_file->size);
99 if(!endpos) {
100 jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file");
101 return JZ_ERR_BAD_FILE_FORMAT;
102 }
103
104 *endpos = 0;
105 jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version);
106 return JZ_SUCCESS;
107}
108
109/** \brief Load the Rockbox bootloader on an X1000 device
110 * \param dev USB device freshly returned by jz_usb_open()
111 * \param filename Path to the "bootloader.target" file
112 * \return either JZ_SUCCESS or an error code
113 */
114int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename)
115{
116 const jz_device_info* dev_info;
117 char spl_filename[32];
118 jz_buffer* spl = NULL, *bootloader = NULL, *info_file = NULL;
119 mtar_t tar;
120 int rc;
121
122 /* In retrospect using a model-dependent archive format was not a good
123 * idea, but it's not worth fixing just yet... */
124 dev_info = jz_get_device_info(type);
125 if(!dev_info)
126 return JZ_ERR_OTHER;
127 /* use of sprintf is safe since file_ext is short */
128 sprintf(spl_filename, "spl.%s", dev_info->file_ext);
129
130 /* Now open the archive */
131 rc = mtar_open(&tar, filename, "rb");
132 if(rc != MTAR_ESUCCESS) {
133 jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", filename, rc);
134 return JZ_ERR_OPEN_FILE;
135 }
136
137 /* Extract all necessary files */
138 rc = get_file(dev->jz, &tar, spl_filename, false, &spl);
139 if(rc != JZ_SUCCESS)
140 goto error;
141
142 rc = get_file(dev->jz, &tar, "bootloader.ucl", true, &bootloader);
143 if(rc != JZ_SUCCESS)
144 goto error;
145
146 rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file);
147 if(rc != JZ_SUCCESS)
148 goto error;
149
150 /* Display the version string */
151 rc = show_version(dev->jz, info_file);
152 if(rc != JZ_SUCCESS)
153 goto error;
154
155 /* Stage1 boot of SPL to set up hardware */
156 rc = run_stage1(dev, spl);
157 if(rc != JZ_SUCCESS)
158 goto error;
159
160 /* Need a bit of time for SPL to handle init */
161 jz_sleepms(500);
162
163 /* Stage2 boot into the bootloader's recovery menu
164 * User has to take manual action from there */
165 rc = run_stage2(dev, bootloader);
166 if(rc != JZ_SUCCESS)
167 goto error;
168
169 rc = JZ_SUCCESS;
170
171 error:
172 if(spl)
173 jz_buffer_free(spl);
174 if(bootloader)
175 jz_buffer_free(bootloader);
176 if(info_file)
177 jz_buffer_free(info_file);
178 mtar_close(&tar);
179 return rc;
180}