950f4bdc02
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
504 lines
15 KiB
C
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;
|
|
}
|