A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2012 Amaury Pouly
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#include <stdio.h>
22#include <stdint.h>
23#include <stdbool.h>
24#include <stdlib.h>
25#include <stddef.h>
26#include <string.h>
27#include <getopt.h>
28#include <stdarg.h>
29#include <ctype.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <fcntl.h>
33#include <unistd.h>
34#include <inttypes.h>
35#include <errno.h>
36#include "rbscsi.h"
37#include "misc.h"
38#include "stmp_scsi.h"
39
40bool g_debug = false;
41bool g_force = false;
42stmp_device_t g_dev_fd;
43
44static void print_hex(void *_buffer, int buffer_size)
45{
46 uint8_t *buffer = _buffer;
47 for(int i = 0; i < buffer_size; i += 16)
48 {
49 for(int j = 0; j < 16; j++)
50 {
51 if(i + j < buffer_size)
52 cprintf(YELLOW, " %02x", buffer[i + j]);
53 else
54 cprintf(YELLOW, " ");
55 }
56 printf(" ");
57 for(int j = 0; j < 16; j++)
58 {
59 if(i + j < buffer_size)
60 cprintf(RED, "%c", isprint(buffer[i + j]) ? buffer[i + j] : '.');
61 else
62 cprintf(RED, " ");
63 }
64 printf("\n");
65 }
66 if(buffer_size == 0)
67 printf("\n");
68}
69
70static const char *get_size_suffix(unsigned long long size)
71{
72 int order = 0;
73 while(size >= 1024)
74 {
75 size /= 1024;
76 order++;
77 }
78 static const char *suffix[] = {"B", "KiB", "MiB", "GiB", "TiB"};
79 return suffix[order];
80}
81
82static float get_size_natural(unsigned long long size)
83{
84 float res = size;
85 while(res >= 1024)
86 res /= 1024;
87 return res;
88}
89
90static void print_ver(struct scsi_stmp_logical_drive_info_version_t *ver)
91{
92 cprintf(YELLOW, "%x.%x.%x\n", ver->major, ver->minor, ver->revision);
93}
94
95static int do_info(void)
96{
97 cprintf(BLUE, "Information\n");
98
99 uint8_t dev_type;
100 char vendor[9];
101 char product[17];
102 int ret = stmp_scsi_inquiry(g_dev_fd, &dev_type, vendor, product);
103 if(ret == 0)
104 {
105 cprintf_field(" Vendor: ", "%s\n", vendor);
106 cprintf_field(" Product: ", "%s\n", product);
107 }
108 else if(g_debug)
109 cprintf(GREY, "Cannot get inquiry data: %d\n", ret);
110
111 struct scsi_stmp_protocol_version_t ver;
112 ret = stmp_get_protocol_version(g_dev_fd, &ver);
113 if(ret == 0)
114 cprintf_field(" Protocol: ", "%x.%x\n", ver.major, ver.minor);
115 else if(g_debug)
116 cprintf(GREY, "Cannot get protocol version: %d\n", ret);
117
118 cprintf(BLUE, "Device\n");
119
120 uint8_t *serial;
121 int serial_len;
122 if(!stmp_get_device_serial(g_dev_fd, &serial, &serial_len))
123 {
124 cprintf_field(" Serial Number:", " ");
125 print_hex(serial, serial_len);
126 free(serial);
127 }
128
129 uint16_t chip_rev;
130 ret = stmp_get_chip_major_rev_id(g_dev_fd, &chip_rev);
131 if(ret)
132 cprintf(GREY, "Cannot get chip major revision id: %d\n", ret);
133 else
134 cprintf_field(" Chip Major Rev ID: ", "%x\n", chip_rev);
135
136 uint16_t rom_rev;
137 ret = stmp_get_rom_rev_id(g_dev_fd, &rom_rev);
138 if(ret)
139 cprintf(GREY, "Cannot get rom revision id: %d\n", ret);
140 else
141 cprintf_field(" ROM Rev ID: ", "%x\n", rom_rev);
142
143 cprintf(BLUE, "Logical Media\n");
144 struct stmp_logical_media_info_t info;
145 ret = stmp_get_logical_media_info(g_dev_fd, &info);
146 if(!ret)
147 {
148 if(info.has.nr_drives)
149 cprintf_field(" Number of drives:", " %u\n", info.nr_drives);
150 if(info.has.size)
151 {
152 cprintf_field(" Media size:", " %" PRIu64 " ", info.size);
153 cprintf(RED, "(%.3f %s)\n", get_size_natural(info.size), get_size_suffix(info.size));
154 }
155 if(info.has.alloc_size)
156 {
157 cprintf_field(" Allocation unit size:", " %lu ", (unsigned long)info.alloc_size);
158 cprintf(RED, "(%.3f %s)\n", get_size_natural(info.alloc_size), get_size_suffix(info.alloc_size));
159 }
160 if(info.has.initialised)
161 cprintf_field(" Initialised:", " %u\n", info.initialised);
162 if(info.has.state)
163 cprintf_field(" State:", " %u\n", info.state);
164 if(info.has.write_protected)
165 cprintf_field(" Write protected:", " %u\n", info.write_protected);
166 if(info.has.type)
167 {
168 cprintf_field(" Type:", " %u ", info.type);
169 cprintf(RED, "(%s)\n", stmp_get_logical_media_type_string(info.type));
170 }
171 if(info.has.serial)
172 {
173 cprintf_field(" Serial:", " ");
174 print_hex(info.serial, info.serial_len);
175 free(info.serial);
176 }
177 if(info.has.system)
178 cprintf_field(" System:", " %u\n", info.system);
179 if(info.has.present)
180 cprintf_field(" Present:", " %u\n", info.present);
181 if(info.has.page_size)
182 {
183 cprintf_field(" Page size:", " %lu ", (unsigned long)info.page_size);
184 cprintf(RED, "(%.3f %s)\n", get_size_natural(info.page_size), get_size_suffix(info.page_size));
185 }
186 if(info.has.vendor)
187 {
188 cprintf_field(" Vendor:", " %u ", info.vendor);
189 cprintf(RED, "(%s)\n", stmp_get_logical_media_vendor_string(info.vendor));
190 }
191 if(info.has.nand_id)
192 {
193 cprintf_field(" Nand ID:", " ");
194 print_hex(info.nand_id, sizeof(info.nand_id));
195 }
196 if(info.has.nr_devices)
197 cprintf_field(" Number of devices:", " %lu\n", (unsigned long)info.nr_devices);
198 }
199 else
200 cprintf(GREY, "Cannot get media info: %d\n", ret);
201
202 struct stmp_logical_media_table_t *table;
203 ret = stmp_get_logical_media_table(g_dev_fd, &table);
204 if(!ret)
205 {
206 cprintf(BLUE, "Logical Media Table\n");
207 for(int i = 0; i < table->header.count; i++)
208 {
209 cprintf(RED, " Drive ");
210 cprintf_field("No: ", "%2x", table->entry[i].drive_no);
211 cprintf_field(" Type: ", "%#x ", table->entry[i].type);
212 cprintf(RED, "(%s)", stmp_get_logical_drive_type_string(table->entry[i].type));
213 cprintf_field(" Tag: ", "%#x ", table->entry[i].tag);
214 cprintf(RED, "(%s)", stmp_get_logical_drive_tag_string(table->entry[i].tag));
215 unsigned long long size = table->entry[i].size;
216 cprintf_field(" Size: ", "%.3f %s", get_size_natural(size), get_size_suffix(size));
217 cprintf(OFF, "\n");
218 }
219
220 for(int i = 0; i < table->header.count; i++)
221 {
222 uint8_t drive = table->entry[i].drive_no;
223 cprintf(BLUE, "Drive ");
224 cprintf(YELLOW, "%02x\n", drive);
225 struct stmp_logical_drive_info_t info;
226 ret = stmp_get_logical_drive_info(g_dev_fd, drive, &info);
227 if(ret)
228 continue;
229 if(info.has.sector_size)
230 {
231 cprintf_field(" Sector size:", " %" PRIu32 " ", info.sector_size);
232 cprintf(RED, "(%.3f %s)\n", get_size_natural(info.sector_size), get_size_suffix(info.sector_size));
233 }
234 if(info.has.erase_size)
235 {
236 cprintf_field(" Erase size:", " %" PRIu32 " ", info.erase_size);
237 cprintf(RED, "(%.3f %s)\n", get_size_natural(info.erase_size), get_size_suffix(info.erase_size));
238 }
239 if(info.has.size)
240 {
241 cprintf_field(" Drive size:", " %" PRIu64 " ", info.size);
242 cprintf(RED, "(%.3f %s)\n", get_size_natural(info.size), get_size_suffix(info.size));
243 }
244 if(info.has.sector_count)
245 cprintf_field(" Sector count:", " %lu\n", (unsigned long)info.sector_count);
246 if(info.has.type)
247 {
248 cprintf_field(" Type:", " %u ", info.type);
249 cprintf(RED, "(%s)\n", stmp_get_logical_drive_type_string(info.type));
250 }
251 if(info.has.tag)
252 {
253 cprintf_field(" Tag:", " %u ", info.tag);
254 cprintf(RED, "(%s)\n", stmp_get_logical_drive_tag_string(info.tag));
255 }
256 if(info.has.component_version)
257 {
258 cprintf_field(" Component version:", " ");
259 print_ver(&info.component_version);
260 }
261 if(info.has.project_version)
262 {
263 cprintf_field(" Project version:", " ");
264 print_ver(&info.project_version);
265 }
266 if(info.has.write_protected)
267 cprintf_field(" Write protected:", " %u\n", info.write_protected);
268 if(info.has.serial)
269 {
270 cprintf_field(" Serial:", " ");
271 print_hex(info.serial, info.serial_len);
272 free(info.serial);
273 }
274 if(info.has.change)
275 cprintf_field(" Change:", " %u\n", info.change);
276 if(info.has.present)
277 cprintf_field(" Present:", " %u\n", info.present);
278 }
279 free(table);
280 }
281 else
282 cprintf(GREY, "Cannot get logical table: %d\n", ret);
283
284 return 0;
285}
286
287struct rw_fw_context_t
288{
289 int tot_size;
290 int cur_size;
291 int last_percent;
292 FILE *f;
293 bool read;
294};
295
296int rw_fw(void *user, void *buf, size_t size)
297{
298 struct rw_fw_context_t *ctx = user;
299 int this_percent = (ctx->cur_size * 100LLU) / ctx->tot_size;
300 if(this_percent != ctx->last_percent && (this_percent % 5) == 0)
301 {
302 cprintf(RED, "%d%%", this_percent);
303 cprintf(YELLOW, "...");
304 fflush(stdout);
305 }
306 ctx->last_percent = this_percent;
307 int ret = -1;
308 if(ctx->read)
309 ret = fread(buf, size, 1, ctx->f);
310 else
311 ret = fwrite(buf, size, 1, ctx->f);
312 ctx->cur_size += size;
313 if(ret != 1)
314 return -1;
315 else
316 return size;
317}
318
319void rw_finish(struct rw_fw_context_t *ctx)
320{
321 if(ctx->last_percent == 100)
322 return;
323 cprintf(RED, "100%%\n");
324}
325
326void do_extract(const char *file)
327{
328 FILE *f = fopen(file, "wb");
329 if(f == NULL)
330 {
331 cprintf(GREY, "Cannot open output file: %s\n", strerror(errno));
332 return;
333 }
334 int ret = stmp_read_firmware(g_dev_fd, NULL, NULL);
335 if(ret < 0)
336 {
337 cprintf(GREY, "Cannot get firmware size: %d\n", ret);
338 return;
339 }
340 struct rw_fw_context_t ctx;
341 ctx.tot_size = ret;
342 ctx.cur_size = 0;
343 ctx.f = f;
344 ctx.last_percent = -1;
345 ctx.read = false;
346 ret = stmp_read_firmware(g_dev_fd, &ctx, &rw_fw);
347 if(ret < 0)
348 cprintf(GREY, "Cannot read firmware: %d\n", ret);
349 rw_finish(&ctx);
350 fclose(f);
351}
352
353void do_write(const char *file, int want_a_brick)
354{
355 if(!want_a_brick)
356 {
357 cprintf(GREY, "Writing a new firmware is a dangerous operation that should be attempted\n");
358 cprintf(GREY, "if you know what you are doing. If you do, please add the --yes-i-want-a-brick\n");
359 cprintf(GREY, "option on the command line and do not complain if you end up with a brick ;)\n");
360 return;
361 }
362 FILE *f = fopen(file, "rb");
363 if(f == NULL)
364 {
365 cprintf(GREY, "Cannot open output file: %s\n", strerror(errno));
366 return;
367 }
368 struct rw_fw_context_t ctx;
369 fseek(f, 0, SEEK_END);
370 ctx.tot_size = ftell(f);
371 fseek(f, 0, SEEK_SET);
372 ctx.cur_size = 0;
373 ctx.f = f;
374 ctx.last_percent = -1;
375 ctx.read = true;
376 int ret = stmp_write_firmware(g_dev_fd, &ctx, &rw_fw);
377 if(ret < 0)
378 cprintf(GREY, "Cannot write firmware: %d\n", ret);
379 rw_finish(&ctx);
380 fclose(f);
381}
382
383static void usage(void)
384{
385 printf("Usage: scsitool [options] <dev>\n");
386 printf("Options:\n");
387 printf(" -f/--force Force to continue on errors\n");
388 printf(" -?/--help Display this message\n");
389 printf(" -d/--debug Display debug messages\n");
390 printf(" -c/--no-color Disable color output\n");
391 printf(" -x/--extract-fw <file> Extract firmware to file\n");
392 printf(" -w/--write-fw <file> Write firmware to device\n");
393 printf(" -i/--info Display device information\n");
394 printf(" --yes-i-want-a-brick Allow the tool to turn your device into a brick\n");
395 exit(1);
396}
397
398static int g_yes_i_want_a_brick = 0;
399
400static void scsi_printf(void *user, const char *fmt, ...)
401{
402 (void)user;
403 if(!g_debug)
404 return;
405 va_list args;
406 va_start(args, fmt);
407 color(GREY);
408 vprintf(fmt, args);
409 va_end(args);
410}
411
412int main(int argc, char **argv)
413{
414 if(argc == 1)
415 usage();
416 const char *extract_fw = NULL;
417 const char *write_fw = NULL;
418 bool info = false;
419 while(1)
420 {
421 static struct option long_options[] =
422 {
423 {"help", no_argument, 0, '?'},
424 {"debug", no_argument, 0, 'd'},
425 {"no-color", no_argument, 0, 'c'},
426 {"force", no_argument, 0, 'f'},
427 {"extract-fw", required_argument, 0, 'x'},
428 {"write-fw", required_argument, 0, 'w'},
429 {"info", no_argument, 0, 'i'},
430 {"yes-i-want-a-brick", no_argument, &g_yes_i_want_a_brick, 1},
431 {0, 0, 0, 0}
432 };
433
434 int c = getopt_long(argc, argv, "?dcfx:iw:", long_options, NULL);
435 if(c == -1)
436 break;
437 switch(c)
438 {
439 case 0:
440 continue;
441 case -1:
442 break;
443 case 'c':
444 enable_color(false);
445 break;
446 case 'd':
447 g_debug = true;
448 break;
449 case 'f':
450 g_force = true;
451 break;
452 case '?':
453 usage();
454 break;
455 case 'x':
456 extract_fw = optarg;
457 break;
458 case 'w':
459 write_fw = optarg;
460 break;
461 case 'i':
462 info = true;
463 break;
464 default:
465 abort();
466 }
467 }
468
469 if(argc - optind != 1)
470 {
471 usage();
472 return 1;
473 }
474
475 int ret = 0;
476 rb_scsi_device_t scsi_dev = rb_scsi_open(argv[optind], g_debug ? RB_SCSI_DEBUG : 0, NULL, scsi_printf);
477 if(scsi_dev == 0)
478 {
479 cprintf(GREY, "Cannot open device\n");
480 ret = 1;
481 goto Lend;
482 }
483 g_dev_fd = stmp_open(scsi_dev, g_debug ? STMP_DEBUG : 0, NULL, scsi_printf);
484 if(g_dev_fd == 0)
485 {
486 cprintf(GREY, "Cannot open stmp device\n");
487 ret = 2;
488 goto Lend;
489 }
490
491 if(extract_fw)
492 do_extract(extract_fw);
493 if(info)
494 do_info();
495 if(write_fw)
496 do_write(write_fw, g_yes_i_want_a_brick);
497
498 stmp_close(g_dev_fd);
499 rb_scsi_close(scsi_dev);
500Lend:
501 color(OFF);
502
503 return ret;
504}