blob: cfc7afdd75d0c1e46f6b96530af9e83a44cd9cae [file] [log] [blame]
/*
* This file is part of the flashprog project.
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include "libflashprog.h"
#include "cli.h"
static void usage(const char *const name, const char *const msg)
{
if (msg)
fprintf(stderr, "\nError: %s\n", msg);
fprintf(stderr, "\nUsage:"
"\t%s [status] <options>\n"
"\t%s list <options>\n"
"\t%s disable <options> [--temporary]\n"
"\t%s enable <options> [--temporary]\n"
"\t%s range <options> [--temporary] <start>,<len>\n"
"\t%s region <options> [--temporary] <region-name>\n",
name, name, name, name, name, name);
fprintf(stderr, "\n"
"A range is specified by two integers, the offset from the start of the flash\n"
"and the length in bytes. A region is specified by name from the layout, see\n"
"layout options below.\n");
print_generic_options(/* layout_options =>*/true);
exit(1);
}
static int parse_wp_range(size_t *const start, size_t *const len, const char *const arg)
{
size_t processed;
if (sscanf(arg, "%zi,%zi%zn", start, len, &processed) != 2)
return -1;
if (*start > SIZE_MAX / 2 || *len > SIZE_MAX / 2)
return -1;
if (processed != strlen(arg))
return -1;
return 0;
}
static void print_wp_range(const char *const prefix,
struct flashprog_flashctx *const flash,
size_t start, size_t len)
{
/* Start address and length */
printf("%sstart=0x%08zx length=0x%08zx ", prefix, start, len);
/* Easily readable description like 'none' or 'lower 1/8' */
size_t chip_len = flashprog_flash_getsize(flash);
if (len == 0) {
printf("(none)\n");
} else if (len == chip_len) {
printf("(all)\n");
} else {
const char *location = "";
if (start == 0)
location = "lower ";
if (start == chip_len - len)
location = "upper ";
/* Remove common factors of 2 to simplify */
/* the (range_len/chip_len) fraction. */
while ((chip_len % 2) == 0 && (len % 2) == 0) {
chip_len /= 2;
len /= 2;
}
printf("(%s%zu/%zu)\n", location, len, chip_len);
}
}
static const char *get_wp_error_str(int err)
{
switch (err) {
case FLASHPROG_WP_ERR_CHIP_UNSUPPORTED:
return "WP operations are not implemented for this chip";
case FLASHPROG_WP_ERR_READ_FAILED:
return "failed to read the current WP configuration";
case FLASHPROG_WP_ERR_WRITE_FAILED:
return "failed to write the new WP configuration";
case FLASHPROG_WP_ERR_VERIFY_FAILED:
return "unexpected WP configuration read back from chip";
case FLASHPROG_WP_ERR_MODE_UNSUPPORTED:
return "the requested protection mode is not supported";
case FLASHPROG_WP_ERR_RANGE_UNSUPPORTED:
return "the requested protection range is not supported";
case FLASHPROG_WP_ERR_RANGE_LIST_UNAVAILABLE:
return "could not determine what protection ranges are available";
case FLASHPROG_WP_ERR_UNSUPPORTED_STATE:
return "can't operate on current WP configuration of the chip";
}
return "unknown WP error";
}
static int wp_print_status(struct flashprog_flashctx *const flash)
{
size_t start, len;
enum flashprog_wp_mode mode;
struct flashprog_wp_cfg *cfg = NULL;
enum flashprog_wp_result ret;
ret = flashprog_wp_cfg_new(&cfg);
if (ret == FLASHPROG_WP_OK)
ret = flashprog_wp_read_cfg(cfg, flash);
if (ret != FLASHPROG_WP_OK) {
fprintf(stderr, "Failed to get WP status: %s\n", get_wp_error_str(ret));
flashprog_wp_cfg_release(cfg);
return 1;
}
flashprog_wp_get_range(&start, &len, cfg);
mode = flashprog_wp_get_mode(cfg);
flashprog_wp_cfg_release(cfg);
print_wp_range("Protection range: ", flash, start, len);
const char *mode_desc;
switch (mode) {
case FLASHPROG_WP_MODE_DISABLED: mode_desc = "disabled"; break;
case FLASHPROG_WP_MODE_HARDWARE: mode_desc = "hardware"; break;
case FLASHPROG_WP_MODE_POWER_CYCLE: mode_desc = "power_cycle"; break;
case FLASHPROG_WP_MODE_PERMANENT: mode_desc = "permanent"; break;
default: mode_desc = "unknown"; break;
}
printf("Protection mode: %s\n", mode_desc);
return 0;
}
static int wp_print_ranges(struct flashprog_flashctx *const flash)
{
struct flashprog_wp_ranges *list;
size_t i;
const enum flashprog_wp_result ret = flashprog_wp_get_available_ranges(&list, flash);
if (ret != FLASHPROG_WP_OK) {
fprintf(stderr, "Failed to get list of protection ranges: %s\n", get_wp_error_str(ret));
return 1;
}
printf("Available protection ranges:\n");
const size_t count = flashprog_wp_ranges_get_count(list);
for (i = 0; i < count; i++) {
size_t start, len;
flashprog_wp_ranges_get_range(&start, &len, list, i);
print_wp_range("\t", flash, start, len);
}
flashprog_wp_ranges_release(list);
return 0;
}
static int wp_apply(struct flashprog_flashctx *const flash,
const bool enable_wp, const bool disable_wp,
const bool set_wp_range, const size_t wp_start,
const size_t wp_len)
{
struct flashprog_wp_cfg *cfg;
enum flashprog_wp_result ret;
ret = flashprog_wp_cfg_new(&cfg);
if (ret == FLASHPROG_WP_OK)
ret = flashprog_wp_read_cfg(cfg, flash);
if (ret != FLASHPROG_WP_OK) {
fprintf(stderr, "Failed to get WP status: %s\n", get_wp_error_str(ret));
flashprog_wp_cfg_release(cfg);
return 1;
}
/* Store current WP mode for printing help text if changing the cfg fails. */
const enum flashprog_wp_mode old_mode = flashprog_wp_get_mode(cfg);
if (set_wp_range)
flashprog_wp_set_range(cfg, wp_start, wp_len);
if (disable_wp)
flashprog_wp_set_mode(cfg, FLASHPROG_WP_MODE_DISABLED);
if (enable_wp)
flashprog_wp_set_mode(cfg, FLASHPROG_WP_MODE_HARDWARE);
ret = flashprog_wp_write_cfg(flash, cfg);
flashprog_wp_cfg_release(cfg);
if (ret != FLASHPROG_WP_OK) {
fprintf(stderr, "Failed to apply new WP settings: %s\n", get_wp_error_str(ret));
if (ret != FLASHPROG_WP_ERR_VERIFY_FAILED)
return 1;
/* Warn user if active WP is likely to have caused failure */
switch (old_mode) {
case FLASHPROG_WP_MODE_HARDWARE:
fprintf(stderr, "Note: hardware status register protection is enabled. "
"The chip's WP# pin must be set to an inactive voltage "
"level to be able to change the WP settings.\n");
break;
case FLASHPROG_WP_MODE_POWER_CYCLE:
fprintf(stderr, "Note: power-cycle status register protection is enabled. "
"A power-off, power-on cycle is usually required to change "
"the chip's WP settings.\n");
break;
case FLASHPROG_WP_MODE_PERMANENT:
fprintf(stderr, "Note: permanent status register protection is enabled. "
"The chip's WP settings cannot be modified.\n");
break;
default:
break;
}
return 1;
}
if (disable_wp)
printf("Disabled hardware protection\n");
if (enable_wp)
printf("Enabled hardware protection\n");
if (set_wp_range)
print_wp_range("Configured protection range: ", flash, wp_start, wp_len);
return 0;
}
int flashprog_wp_main(int argc, char *argv[])
{
static const char optstring[] = "+p:c:Vo:hl:";
static const struct option long_options[] = {
{"programmer", 1, NULL, 'p'},
{"chip", 1, NULL, 'c'},
{"verbose", 0, NULL, 'V'},
{"output", 1, NULL, 'o'},
{"help", 0, NULL, 'h'},
{"layout", 1, NULL, 'l'},
{"ifd", 0, NULL, OPTION_IFD},
{"fmap", 0, NULL, OPTION_FMAP},
{"fmap-file", 1, NULL, OPTION_FMAP_FILE},
{"temporary", 0, NULL, OPTION_CONFIG_VOLATILE},
{"status", 0, NULL, OPTION_WP_STATUS},
{"list", 0, NULL, OPTION_WP_LIST},
{"range", 0, NULL, OPTION_WP_SET_RANGE},
{"region", 0, NULL, OPTION_WP_SET_REGION},
{"enable", 0, NULL, OPTION_WP_ENABLE},
{"disable", 0, NULL, OPTION_WP_DISABLE},
{NULL, 0, NULL, 0},
};
static const struct opt_command cmd_options[] = {
{"status", OPTION_WP_STATUS},
{"list", OPTION_WP_LIST},
{"range", OPTION_WP_SET_RANGE},
{"region", OPTION_WP_SET_REGION},
{"enable", OPTION_WP_ENABLE},
{"disable", OPTION_WP_DISABLE},
{NULL, 0},
};
unsigned int ops = 0;
int ret = 1, opt;
struct log_args log_args = { FLASHPROG_MSG_INFO, FLASHPROG_MSG_DEBUG2, NULL };
struct flash_args flash_args = { 0 };
struct layout_args layout_args = { 0 };
bool volat1le = false;
bool enable_wp = false, disable_wp = false, print_wp_status = false;
bool set_wp_range = false, set_wp_region = false, print_wp_ranges = false;
size_t wp_start = 0, wp_len = 0;
char *wp_region = NULL;
if (cli_init()) /* TODO: Can be moved below argument parsing once usage() uses `stderr` directly. */
goto free_ret;
if (argc < 2)
usage(argv[0], NULL);
while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1 ||
(opt = getopt_command(argc, argv, cmd_options)) != -1) {
switch (opt) {
case 'V':
case 'o':
ret = cli_parse_log_args(&log_args, opt, optarg);
if (ret == 1)
usage(argv[0], NULL);
else if (ret)
goto free_ret;
break;
case 'p':
case 'c':
ret = cli_parse_flash_args(&flash_args, opt, optarg);
if (ret == 1)
usage(argv[0], NULL);
else if (ret)
goto free_ret;
break;
case OPTION_LAYOUT:
case OPTION_IFD:
case OPTION_FMAP:
case OPTION_FMAP_FILE:
ret = cli_parse_layout_args(&layout_args, opt, optarg);
if (ret == 1)
usage(argv[0], NULL);
else if (ret)
goto free_ret;
break;
case OPTION_CONFIG_VOLATILE:
volat1le = true;
break;
case OPTION_WP_STATUS:
print_wp_status = true;
++ops;
break;
case OPTION_WP_LIST:
print_wp_ranges = true;
++ops;
break;
case OPTION_WP_SET_RANGE:
set_wp_range = true;
++ops;
break;
case OPTION_WP_SET_REGION:
set_wp_region = true;
++ops;
break;
case OPTION_WP_ENABLE:
enable_wp = true;
++ops;
break;
case OPTION_WP_DISABLE:
disable_wp = true;
++ops;
break;
case '?':
case 'h':
usage(argv[0], NULL);
break;
}
}
if (!ops) {
print_wp_status = true;
++ops;
}
if (ops > 1)
usage(argv[0], "Only one operation may be specified.");
if (!enable_wp && !disable_wp && !set_wp_range && !set_wp_region && volat1le)
usage(argv[0], "`--temporary' may only be specified for write operations.");
ret = 1;
if (set_wp_range) {
if (optind != argc - 1)
usage(argv[0], "`range' requires exactly one argument.");
if (parse_wp_range(&wp_start, &wp_len, argv[optind++]) < 0)
usage(argv[0], "Incorrect wp-range arguments provided.");
} else if (set_wp_region) {
if (optind != argc - 1)
usage(argv[0], "`region' requires exactly one argument.");
wp_region = strdup(argv[optind++]);
if (!wp_region) {
fprintf(stderr, "Out of memory!\n");
goto free_ret;
}
} else if (optind < argc) {
usage(argv[0], "Extra parameter found.");
}
if (!flash_args.prog_name)
usage(argv[0], "No programmer specified.");
struct flashprog_programmer *prog;
struct flashprog_flashctx *flash;
struct flashprog_layout *layout = NULL;
if (log_args.logfile && open_logfile(log_args.logfile))
goto free_ret;
verbose_screen = log_args.screen_level;
verbose_logfile = log_args.logfile_level;
start_logging();
if (flashprog_programmer_init(&prog, flash_args.prog_name, flash_args.prog_args))
goto free_ret;
ret = flashprog_flash_probe(&flash, prog, flash_args.chip);
if (ret == 3) {
fprintf(stderr, "Multiple flash chip definitions match the detected chip.\n"
"Please specify which chip definition to use with the -c <chipname> option.\n");
} else if (ret) {
fprintf(stderr, "No EEPROM/flash device found.\n");
goto shutdown_ret;
}
flashprog_flag_set(flash, FLASHPROG_FLAG_NON_VOLATILE_WRSR, !volat1le);
if (print_wp_status)
ret = wp_print_status(flash);
if (print_wp_ranges)
ret = wp_print_ranges(flash);
if (set_wp_region) {
ret = 1;
if (cli_process_layout_args(&layout, flash, &layout_args)) {
fprintf(stderr, "Failed to read layout.\n");
goto release_ret;
}
if (!layout) {
fprintf(stderr, "Error: `--region' operation requires a layout.\n");
goto release_ret;
}
if (flashprog_layout_get_region_range(layout, wp_region, &wp_start, &wp_len)) {
fprintf(stderr, "Cannot find region '%s'.\n", wp_region);
goto release_ret;
}
set_wp_range = true;
}
if (set_wp_range || enable_wp || disable_wp)
ret = wp_apply(flash, enable_wp, disable_wp, set_wp_range, wp_start, wp_len);
release_ret:
flashprog_layout_release(layout);
flashprog_flash_release(flash);
shutdown_ret:
flashprog_programmer_shutdown(prog);
free_ret:
free(wp_region);
free(layout_args.fmapfile);
free(layout_args.layoutfile);
free(flash_args.chip);
free(flash_args.prog_args);
free(flash_args.prog_name);
free(log_args.logfile);
close_logfile();
return ret;
}