rockbox/utils/imxtools/scsitools/scsitool.c
Amaury Pouly 950f4bdc02 imxtools: move firmware read/write to library
Split the ugly firmware read/write into a API function and a much simplified code.
Also the code can now report progress.

Change-Id: I3f998eaf0c067c6da42b1d2dd9c5a5bf43c6915d
2017-01-07 15:52:33 +01:00

504 lines
15 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2012 Amaury Pouly
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <getopt.h>
#include <stdarg.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
#include <errno.h>
#include "rbscsi.h"
#include "misc.h"
#include "stmp_scsi.h"
bool g_debug = false;
bool g_force = false;
stmp_device_t g_dev_fd;
static void print_hex(void *_buffer, int buffer_size)
{
uint8_t *buffer = _buffer;
for(int i = 0; i < buffer_size; i += 16)
{
for(int j = 0; j < 16; j++)
{
if(i + j < buffer_size)
cprintf(YELLOW, " %02x", buffer[i + j]);
else
cprintf(YELLOW, " ");
}
printf(" ");
for(int j = 0; j < 16; j++)
{
if(i + j < buffer_size)
cprintf(RED, "%c", isprint(buffer[i + j]) ? buffer[i + j] : '.');
else
cprintf(RED, " ");
}
printf("\n");
}
if(buffer_size == 0)
printf("\n");
}
static const char *get_size_suffix(unsigned long long size)
{
int order = 0;
while(size >= 1024)
{
size /= 1024;
order++;
}
static const char *suffix[] = {"B", "KiB", "MiB", "GiB", "TiB"};
return suffix[order];
}
static float get_size_natural(unsigned long long size)
{
float res = size;
while(res >= 1024)
res /= 1024;
return res;
}
static void print_ver(struct scsi_stmp_logical_drive_info_version_t *ver)
{
cprintf(YELLOW, "%x.%x.%x\n", ver->major, ver->minor, ver->revision);
}
static int do_info(void)
{
cprintf(BLUE, "Information\n");
uint8_t dev_type;
char vendor[9];
char product[17];
int ret = stmp_scsi_inquiry(g_dev_fd, &dev_type, vendor, product);
if(ret == 0)
{
cprintf_field(" Vendor: ", "%s\n", vendor);
cprintf_field(" Product: ", "%s\n", product);
}
else if(g_debug)
cprintf(GREY, "Cannot get inquiry data: %d\n", ret);
struct scsi_stmp_protocol_version_t ver;
ret = stmp_get_protocol_version(g_dev_fd, &ver);
if(ret == 0)
cprintf_field(" Protocol: ", "%x.%x\n", ver.major, ver.minor);
else if(g_debug)
cprintf(GREY, "Cannot get protocol version: %d\n", ret);
cprintf(BLUE, "Device\n");
uint8_t *serial;
int serial_len;
if(!stmp_get_device_serial(g_dev_fd, &serial, &serial_len))
{
cprintf_field(" Serial Number:", " ");
print_hex(serial, serial_len);
free(serial);
}
uint16_t chip_rev;
ret = stmp_get_chip_major_rev_id(g_dev_fd, &chip_rev);
if(ret)
cprintf(GREY, "Cannot get chip major revision id: %d\n", ret);
else
cprintf_field(" Chip Major Rev ID: ", "%x\n", chip_rev);
uint16_t rom_rev;
ret = stmp_get_rom_rev_id(g_dev_fd, &rom_rev);
if(ret)
cprintf(GREY, "Cannot get rom revision id: %d\n", ret);
else
cprintf_field(" ROM Rev ID: ", "%x\n", rom_rev);
cprintf(BLUE, "Logical Media\n");
struct stmp_logical_media_info_t info;
ret = stmp_get_logical_media_info(g_dev_fd, &info);
if(!ret)
{
if(info.has.nr_drives)
cprintf_field(" Number of drives:", " %u\n", info.nr_drives);
if(info.has.size)
{
cprintf_field(" Media size:", " %" PRIu64 " ", info.size);
cprintf(RED, "(%.3f %s)\n", get_size_natural(info.size), get_size_suffix(info.size));
}
if(info.has.alloc_size)
{
cprintf_field(" Allocation unit size:", " %lu ", (unsigned long)info.alloc_size);
cprintf(RED, "(%.3f %s)\n", get_size_natural(info.alloc_size), get_size_suffix(info.alloc_size));
}
if(info.has.initialised)
cprintf_field(" Initialised:", " %u\n", info.initialised);
if(info.has.state)
cprintf_field(" State:", " %u\n", info.state);
if(info.has.write_protected)
cprintf_field(" Write protected:", " %u\n", info.write_protected);
if(info.has.type)
{
cprintf_field(" Type:", " %u ", info.type);
cprintf(RED, "(%s)\n", stmp_get_logical_media_type_string(info.type));
}
if(info.has.serial)
{
cprintf_field(" Serial:", " ");
print_hex(info.serial, info.serial_len);
free(info.serial);
}
if(info.has.system)
cprintf_field(" System:", " %u\n", info.system);
if(info.has.present)
cprintf_field(" Present:", " %u\n", info.present);
if(info.has.page_size)
{
cprintf_field(" Page size:", " %lu ", (unsigned long)info.page_size);
cprintf(RED, "(%.3f %s)\n", get_size_natural(info.page_size), get_size_suffix(info.page_size));
}
if(info.has.vendor)
{
cprintf_field(" Vendor:", " %u ", info.vendor);
cprintf(RED, "(%s)\n", stmp_get_logical_media_vendor_string(info.vendor));
}
if(info.has.nand_id)
{
cprintf_field(" Nand ID:", " ");
print_hex(info.nand_id, sizeof(info.nand_id));
}
if(info.has.nr_devices)
cprintf_field(" Number of devices:", " %lu\n", (unsigned long)info.nr_devices);
}
else
cprintf(GREY, "Cannot get media info: %d\n", ret);
struct stmp_logical_media_table_t *table;
ret = stmp_get_logical_media_table(g_dev_fd, &table);
if(!ret)
{
cprintf(BLUE, "Logical Media Table\n");
for(int i = 0; i < table->header.count; i++)
{
cprintf(RED, " Drive ");
cprintf_field("No: ", "%2x", table->entry[i].drive_no);
cprintf_field(" Type: ", "%#x ", table->entry[i].type);
cprintf(RED, "(%s)", stmp_get_logical_drive_type_string(table->entry[i].type));
cprintf_field(" Tag: ", "%#x ", table->entry[i].tag);
cprintf(RED, "(%s)", stmp_get_logical_drive_tag_string(table->entry[i].tag));
unsigned long long size = table->entry[i].size;
cprintf_field(" Size: ", "%.3f %s", get_size_natural(size), get_size_suffix(size));
cprintf(OFF, "\n");
}
for(int i = 0; i < table->header.count; i++)
{
uint8_t drive = table->entry[i].drive_no;
cprintf(BLUE, "Drive ");
cprintf(YELLOW, "%02x\n", drive);
struct stmp_logical_drive_info_t info;
ret = stmp_get_logical_drive_info(g_dev_fd, drive, &info);
if(ret)
continue;
if(info.has.sector_size)
{
cprintf_field(" Sector size:", " %" PRIu32 " ", info.sector_size);
cprintf(RED, "(%.3f %s)\n", get_size_natural(info.sector_size), get_size_suffix(info.sector_size));
}
if(info.has.erase_size)
{
cprintf_field(" Erase size:", " %" PRIu32 " ", info.erase_size);
cprintf(RED, "(%.3f %s)\n", get_size_natural(info.erase_size), get_size_suffix(info.erase_size));
}
if(info.has.size)
{
cprintf_field(" Drive size:", " %" PRIu64 " ", info.size);
cprintf(RED, "(%.3f %s)\n", get_size_natural(info.size), get_size_suffix(info.size));
}
if(info.has.sector_count)
cprintf_field(" Sector count:", " %lu\n", (unsigned long)info.sector_count);
if(info.has.type)
{
cprintf_field(" Type:", " %u ", info.type);
cprintf(RED, "(%s)\n", stmp_get_logical_drive_type_string(info.type));
}
if(info.has.tag)
{
cprintf_field(" Tag:", " %u ", info.tag);
cprintf(RED, "(%s)\n", stmp_get_logical_drive_tag_string(info.tag));
}
if(info.has.component_version)
{
cprintf_field(" Component version:", " ");
print_ver(&info.component_version);
}
if(info.has.project_version)
{
cprintf_field(" Project version:", " ");
print_ver(&info.project_version);
}
if(info.has.write_protected)
cprintf_field(" Write protected:", " %u\n", info.write_protected);
if(info.has.serial)
{
cprintf_field(" Serial:", " ");
print_hex(info.serial, info.serial_len);
free(info.serial);
}
if(info.has.change)
cprintf_field(" Change:", " %u\n", info.change);
if(info.has.present)
cprintf_field(" Present:", " %u\n", info.present);
}
free(table);
}
else
cprintf(GREY, "Cannot get logical table: %d\n", ret);
return 0;
}
struct rw_fw_context_t
{
int tot_size;
int cur_size;
int last_percent;
FILE *f;
bool read;
};
int rw_fw(void *user, void *buf, size_t size)
{
struct rw_fw_context_t *ctx = user;
int this_percent = (ctx->cur_size * 100LLU) / ctx->tot_size;
if(this_percent != ctx->last_percent && (this_percent % 5) == 0)
{
cprintf(RED, "%d%%", this_percent);
cprintf(YELLOW, "...");
fflush(stdout);
}
ctx->last_percent = this_percent;
int ret = -1;
if(ctx->read)
ret = fread(buf, size, 1, ctx->f);
else
ret = fwrite(buf, size, 1, ctx->f);
ctx->cur_size += size;
if(ret != 1)
return -1;
else
return size;
}
void rw_finish(struct rw_fw_context_t *ctx)
{
if(ctx->last_percent == 100)
return;
cprintf(RED, "100%%\n");
}
void do_extract(const char *file)
{
FILE *f = fopen(file, "wb");
if(f == NULL)
{
cprintf(GREY, "Cannot open output file: %s\n", strerror(errno));
return;
}
int ret = stmp_read_firmware(g_dev_fd, NULL, NULL);
if(ret < 0)
{
cprintf(GREY, "Cannot get firmware size: %d\n", ret);
return;
}
struct rw_fw_context_t ctx;
ctx.tot_size = ret;
ctx.cur_size = 0;
ctx.f = f;
ctx.last_percent = -1;
ctx.read = false;
ret = stmp_read_firmware(g_dev_fd, &ctx, &rw_fw);
if(ret < 0)
cprintf(GREY, "Cannot read firmware: %d\n", ret);
rw_finish(&ctx);
fclose(f);
}
void do_write(const char *file, int want_a_brick)
{
if(!want_a_brick)
{
cprintf(GREY, "Writing a new firmware is a dangerous operation that should be attempted\n");
cprintf(GREY, "if you know what you are doing. If you do, please add the --yes-i-want-a-brick\n");
cprintf(GREY, "option on the command line and do not complain if you end up with a brick ;)\n");
return;
}
FILE *f = fopen(file, "rb");
if(f == NULL)
{
cprintf(GREY, "Cannot open output file: %s\n", strerror(errno));
return;
}
struct rw_fw_context_t ctx;
fseek(f, 0, SEEK_END);
ctx.tot_size = ftell(f);
fseek(f, 0, SEEK_SET);
ctx.cur_size = 0;
ctx.f = f;
ctx.last_percent = -1;
ctx.read = true;
int ret = stmp_write_firmware(g_dev_fd, &ctx, &rw_fw);
if(ret < 0)
cprintf(GREY, "Cannot write firmware: %d\n", ret);
rw_finish(&ctx);
fclose(f);
}
static void usage(void)
{
printf("Usage: scsitool [options] <dev>\n");
printf("Options:\n");
printf(" -f/--force Force to continue on errors\n");
printf(" -?/--help Display this message\n");
printf(" -d/--debug Display debug messages\n");
printf(" -c/--no-color Disable color output\n");
printf(" -x/--extract-fw <file> Extract firmware to file\n");
printf(" -w/--write-fw <file> Write firmware to device\n");
printf(" -i/--info Display device information\n");
printf(" --yes-i-want-a-brick Allow the tool to turn your device into a brick\n");
exit(1);
}
static int g_yes_i_want_a_brick = 0;
static void scsi_printf(void *user, const char *fmt, ...)
{
(void)user;
if(!g_debug)
return;
va_list args;
va_start(args, fmt);
color(GREY);
vprintf(fmt, args);
va_end(args);
}
int main(int argc, char **argv)
{
if(argc == 1)
usage();
const char *extract_fw = NULL;
const char *write_fw = NULL;
bool info = false;
while(1)
{
static struct option long_options[] =
{
{"help", no_argument, 0, '?'},
{"debug", no_argument, 0, 'd'},
{"no-color", no_argument, 0, 'c'},
{"force", no_argument, 0, 'f'},
{"extract-fw", required_argument, 0, 'x'},
{"write-fw", required_argument, 0, 'w'},
{"info", no_argument, 0, 'i'},
{"yes-i-want-a-brick", no_argument, &g_yes_i_want_a_brick, 1},
{0, 0, 0, 0}
};
int c = getopt_long(argc, argv, "?dcfx:iw:", long_options, NULL);
if(c == -1)
break;
switch(c)
{
case 0:
continue;
case -1:
break;
case 'c':
enable_color(false);
break;
case 'd':
g_debug = true;
break;
case 'f':
g_force = true;
break;
case '?':
usage();
break;
case 'x':
extract_fw = optarg;
break;
case 'w':
write_fw = optarg;
break;
case 'i':
info = true;
break;
default:
abort();
}
}
if(argc - optind != 1)
{
usage();
return 1;
}
int ret = 0;
rb_scsi_device_t scsi_dev = rb_scsi_open(argv[optind], g_debug ? RB_SCSI_DEBUG : 0, NULL, scsi_printf);
if(scsi_dev == 0)
{
cprintf(GREY, "Cannot open device\n");
ret = 1;
goto Lend;
}
g_dev_fd = stmp_open(scsi_dev, g_debug ? STMP_DEBUG : 0, NULL, scsi_printf);
if(g_dev_fd == 0)
{
cprintf(GREY, "Cannot open stmp device\n");
ret = 2;
goto Lend;
}
if(extract_fw)
do_extract(extract_fw);
if(info)
do_info();
if(write_fw)
do_write(write_fw, g_yes_i_want_a_brick);
stmp_close(g_dev_fd);
rb_scsi_close(scsi_dev);
Lend:
color(OFF);
return ret;
}