cli: Add new write-protect CLI
Add a new write-protect CLI that is based on the classic-CLI feature
in flashrom/master. The syntax is slighty different: With the new
CLI wrapper, we can either call it as `flashprog write-protect` or
`flashprog wp`. To keep the CLI code clean, we allow only one write-
protection operation per call.
For instance, the write-protection status can then be queried like
this:
$ flashprog wp status -p ch341a_spi
Change-Id: I32818b58c9db939719913fc63063c41a27876554
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/72991
diff --git a/Makefile b/Makefile
index 65b808b..cc6e4d4 100644
--- a/Makefile
+++ b/Makefile
@@ -401,7 +401,7 @@
###############################################################################
# Frontend related stuff.
-CLI_OBJS = cli.o cli_config.o cli_classic.o cli_output.o cli_common.o print.o
+CLI_OBJS = cli.o cli_config.o cli_wp.o cli_classic.o cli_output.o cli_common.o print.o
# By default version information will be fetched from Git if available.
# Otherwise, versioninfo.inc stores the metadata required to build a
@@ -925,7 +925,7 @@
endif
OBJS = $(CHIP_OBJS) $(PROGRAMMER_OBJS) $(LIB_OBJS)
-MANS = $(PROGRAM).8 $(PROGRAM)-config.8
+MANS = $(PROGRAM).8 $(PROGRAM)-config.8 $(PROGRAM)-write-protect.8
all: $(PROGRAM)$(EXEC_SUFFIX) $(MANS)
ifeq ($(ARCH), x86)
diff --git a/cli.c b/cli.c
index f1d483a..49e0c60 100644
--- a/cli.c
+++ b/cli.c
@@ -28,6 +28,8 @@
{ "prog", flashprog_classic_main },
{ "cfg", flashprog_config_main },
{ "config", flashprog_config_main },
+ { "wp", flashprog_wp_main },
+ { "write-protect", flashprog_wp_main },
};
static void usage(const char *const name)
@@ -37,6 +39,7 @@
" prog Standard memory operations\n"
" (read/erase/write/verify)\n"
" cfg | config Status/config register operations\n"
+ " wp | write-protect Write-protection operations\n"
"\n"
"The default is 'prog'. See `%s <command> --help`\n"
"for further instructions.\n\n", name);
diff --git a/cli_common.c b/cli_common.c
index 8257fcf..1d43ad2 100644
--- a/cli_common.c
+++ b/cli_common.c
@@ -230,7 +230,7 @@
return -1;
}
-void print_generic_options(void)
+void print_generic_options(const bool layout_options)
{
fprintf(stderr, "\n"
"Where generic <options> are\n"
@@ -241,6 +241,15 @@
" -V | --verbose more verbose output\n"
" -o | --output <logfile> log output to <logfile>\n"
" -h | --help print help text\n");
+
+ if (!layout_options)
+ return;
+ fprintf(stderr, "\n"
+ "and layout <options> are\n"
+ " -l | --layout <layoutfile> read ROM layout from <layoutfile>\n"
+ " --fmap-file <fmapfile> read ROM layout from fmap in <fmapfile>\n"
+ " --fmap read ROM layout from fmap embedded in ROM\n"
+ " --ifd read layout from an Intel Flash Descriptor\n");
}
void print_chip_support_status(const struct flashchip *chip)
diff --git a/cli_config.c b/cli_config.c
index 3ffa048..7fbd310 100644
--- a/cli_config.c
+++ b/cli_config.c
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
+#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
@@ -88,7 +89,7 @@
"\t%s [get] <options> <setting>\n"
"\t%s set <options> [--temporary] <setting> <value>\n",
name, name);
- print_generic_options();
+ print_generic_options(/* layout_options =>*/false);
fprintf(stderr, "\n<setting> can be\n"
" qe | quad-enable Quad-Enable (QE) bit\n"
"\nand <value> can be `true', `false', or a number.\n"
diff --git a/cli_wp.c b/cli_wp.c
new file mode 100644
index 0000000..cfc7afd
--- /dev/null
+++ b/cli_wp.c
@@ -0,0 +1,458 @@
+/*
+ * 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;
+}
diff --git a/flashprog-write-protect.8.tmpl b/flashprog-write-protect.8.tmpl
new file mode 100644
index 0000000..d536374
--- /dev/null
+++ b/flashprog-write-protect.8.tmpl
@@ -0,0 +1,241 @@
+.\" Load the www device when using groff; provide a fallback for groff's MTO macro that formats email addresses.
+.ie \n[.g] \
+. mso www.tmac
+.el \{
+. de MTO
+ \\$2 \(la\\$1 \(ra\\$3 \
+. .
+.\}
+.\" Create wrappers for .MTO and .URL that print only text on systems w/o groff or if not outputting to a HTML
+.\" device. To that end we need to distinguish HTML output on groff from other configurations first.
+.nr groffhtml 0
+.if \n[.g] \
+. if "\*[.T]"html" \
+. nr groffhtml 1
+.\" For code reuse it would be nice to have a single wrapper that gets its target macro as parameter.
+.\" However, this did not work out with NetBSD's and OpenBSD's groff...
+.de URLB
+. ie (\n[groffhtml]==1) \{\
+. URL \\$@
+. \}
+. el \{\
+. ie "\\$2"" \{\
+. BR "\\$1" "\\$3"
+. \}
+. el \{\
+. RB "\\$2 \(la" "\\$1" "\(ra\\$3"
+. \}
+. \}
+..
+.de MTOB
+. ie (\n[groffhtml]==1) \{\
+. MTO \\$@
+. \}
+. el \{\
+. ie "\\$2"" \{\
+. BR "\\$1" "\\$3"
+. \}
+. el \{\
+. RB "\\$2 \(la" "\\$1" "\(ra\\$3"
+. \}
+. \}
+..
+.TH FLASHPROG-WRITE-PROTECT 8 "@MAN_DATE@" "flashprog-write-protect-@VERSION@" "@MAN_DATE@"
+
+.SH NAME
+flashprog-write-protect \- control write-protection settings of flash chips
+
+.SH SYNOPSIS
+.I flashprog write-protect \fR[\fIstatus\fR] <options>
+.br
+.I flashprog write-protect \ list \ \ \ \fR<options>
+.br
+.I flashprog write-protect \ disable \fR<options> [\fB\-\-temporary\fR]
+.br
+.I flashprog write-protect \ enable \ \fR<options> [\fB\-\-temporary\fR]
+.br
+.I flashprog write-protect \ range \ \ \fR<options> [\fB\-\-temporary\fR] <start>\fB,\fR<len>
+.br
+.I flashprog write-protect \ region \ \fR<options> [\fB\-\-temporary\fR] <region-name>
+.sp
+Where generic <options> are:
+.RS 4
+\fB\-p\fR <programmername>[:<parameters>] [\fB\-c\fR <chipname>]
+.br
+[\fB\-V\fR[\fBV\fR[\fBV\fR]]] [\fB-o\fR <logfile>] [\fB\-h\fR]
+.RE
+.sp
+and layout <options> are:
+.RS 4
+[(\fB-l\fR|\fB--layout\fR) <layout-file>|\fB--fmap\fR <fmap-file>|\fB--fmap\fR|\fB--ifd\fR]
+.RE
+
+.SH DESCRIPTION
+.B flashprog-write-protect
+is a utility for reading and writing the write-protection settings
+of flash chips. Currently, it supports only block protection of SPI NOR
+chips.
+
+.SH OPERATIONS
+You can specify one operation per call.
+.B status
+is the default operation.
+.PP
+.B status
+.RS 4
+Shows the write-protection state, including the currently
+programmed protection range.
+.RE
+.PP
+.B list
+.RS 4
+Prints a list of write-protection ranges supported for the
+flash chip.
+.RE
+.PP
+.B disable
+.RS 4
+Disables write protection locks. The configured range usually
+stays as is, but it will be possible to override it.
+.RE
+.PP
+.B enable
+.RS 4
+Enables write protection locks. The write-protection range
+should be set before running the enable operation.
+.RE
+.PP
+.BR range " <start>,<len>"
+.RS 4
+Configures the protected range.
+.BR start " and " length
+specify the range in decimal, octal (\fB0\fR prefix),
+or hexadecimal (\fB0x\fR prefix) numbers of bytes.
+Any zero-length range will unprotect the entire flash
+(e.g. \fBrange 0,0\fR).
+.RE
+.PP
+.BR region " <region-name>"
+.RS 4
+Configures the protected range, matching a region of the loaded
+layout (from a file or flash, see the respective option-descriptions in
+.MR flashprog 8
+for possible layout sources).
+.RE
+
+.SH OPTIONS
+All operations require the
+.B -p/--programmer
+option to be used (please see
+.MR flashprog 8
+for more information on programmer support and parameters).
+.PP
+.BR \-p ", " \-\-programmer " <name>[" : "<parameter>[" , "<parameter>]...]"
+.RS 4
+Specify the programmer device. This is mandatory for all operations.
+Please see the
+.MR flashprog 8
+manual for a list of currently supported programmers and their parameters.
+.RE
+.PP
+.BR \-c ", " \-\-chip " <chipname>"
+.RS 4
+Probe only for the specified flash ROM chip. This option takes the chip name as
+printed by
+.B "flashprog \-L"
+without the vendor name as parameter. Please note that the chip name is
+case sensitive.
+.RE
+.PP
+.BR \-V ", " \-\-verbose
+.RS 4
+More verbose output. This option can be supplied multiple times
+(max. 3 times, i.e.
+.BR \-VVV )
+for even more debug output.
+.RE
+.PP
+.BR \-o ", " \-\-output " <logfile>"
+.RS 4
+Save the full debug log to
+.BR <logfile> .
+If the file already exists, it will be overwritten. This is the recommended
+way to gather logs from flashprog because they will be verbose even if the
+on-screen messages are not verbose and don't require output redirection.
+.RE
+.PP
+.BR \-h ", " \-\-help
+.RS 4
+Show a help text and exit.
+.RE
+.PP
+.RB ( -l | --layout ") <layout-file>, " --fmap-file " <fmap-file>, " --fmap ", " --ifd
+.RS 4
+Please see the
+.MR flashrom 8
+manual for information about layout files and other layout sources.
+.RE
+.PP
+.B \-\-temporary
+.RS 4
+When the
+.B \-\-temporary
+option is provided for any operation that alters the flash chip's
+configuration, flashprog will attempt to write a temporary
+value that is not stored to flash. This requires special support
+by the flash chip for a volatile write status register command.
+The new value will be lost upon reset of the flash chip. Hence,
+it is futile to use this with external programmers that toggle
+power to the flash chip (e.g. Dediprog).
+.RE
+
+.SH EXAMPLES
+To just print the current write-protection state of the internal
+BIOS flash:
+.sp
+.RS 2
+.B flashprog write-protect -p internal
+.sp
+.RE
+or
+.sp
+.RS 2
+.B flashprog write-protect status -p internal
+.sp
+.RE
+To temporarily enable the currently configured range:
+.sp
+.RS 2
+.B flashprog write-protect enable -p internal --temporary
+.RE
+
+.SH EXIT STATUS
+flashprog exits with 0 on success, 1 on most failures but with 3 if a call to mmap() fails.
+
+.SH REQUIREMENTS
+flashprog needs different access permissions for different programmers.
+See this section in the
+.MR flashprog 8
+manual for details.
+
+.SH BUGS
+You can report bugs, ask us questions or send success reports
+via our communication channels listed here:
+.URLB "https://www.flashprog.org/Contact" "" .
+.sp
+
+.SH LICENSE
+.B flashprog
+is covered by the GNU General Public License (GPL), version 2. Some files are
+additionally available under any later version of the GPL.
+
+.SH COPYRIGHT
+.br
+Please see the individual files.
+.PP
+This manual page was written by Nico Huber and is derived from the
+flashprog(8) manual. It is licensed under the terms of the GNU GPL
+(version 2 or later).
+
+.SH SEE ALSO
+.MR flashprog 8
diff --git a/flashprog.8.tmpl b/flashprog.8.tmpl
index d9a59f9..9ebcf60 100644
--- a/flashprog.8.tmpl
+++ b/flashprog.8.tmpl
@@ -46,12 +46,13 @@
.SH SYNOPSIS
Flashprog supports multiple command modes:
.sp
-.B flashprog \fR([\fBprog\fR]|\fBconfig\fR|\fBcfg\fR)
+.BR flashprog " ([" prog ]| config | cfg | write-protect | wp )
.sp
With
.B prog
being the default and described in this manual. For the other commands, see
-.MR flashprog-config 8 .
+.MR flashprog-config 8 ", and"
+.MR flashprog-write-protect 8 .
.sp
.B flashprog \fR[\fB\-h\fR|\fB\-R\fR|\fB\-L\fR|\fB\-z\fR|
\fB\-p\fR <programmername>[:<parameters>] [\fB\-c\fR <chipname>]
@@ -1738,4 +1739,5 @@
It is licensed under the terms of the GNU GPL (version 2 or later).
.SH SEE ALSO
-.MR flashprog-config 8
+.MR flashprog-config 8 ,
+.MR flashprog-write-protect 8
diff --git a/include/cli.h b/include/cli.h
index 69b0146..1fbdef6 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -15,6 +15,8 @@
#ifndef FLASHPROG_CLI_H
#define FLASHPROG_CLI_H
+#include <stdbool.h>
+
#include "libflashprog.h"
enum {
@@ -35,6 +37,12 @@
OPTION_CONFIG_GET,
OPTION_CONFIG_SET,
OPTION_CONFIG_VOLATILE,
+ OPTION_WP_STATUS,
+ OPTION_WP_SET_RANGE,
+ OPTION_WP_SET_REGION,
+ OPTION_WP_ENABLE,
+ OPTION_WP_DISABLE,
+ OPTION_WP_LIST,
};
struct log_args {
@@ -67,6 +75,7 @@
int flashprog_classic_main(int argc, char *argv[]);
int flashprog_config_main(int argc, char *argv[]);
+int flashprog_wp_main(int argc, char *argv[]);
extern enum flashprog_log_level verbose_screen;
extern enum flashprog_log_level verbose_logfile;
@@ -81,6 +90,6 @@
};
int getopt_command(int argc, char *const argv[], const struct opt_command *);
-void print_generic_options(void);
+void print_generic_options(bool layout_options);
#endif
diff --git a/meson.build b/meson.build
index 7a3d35c..f87bac5 100644
--- a/meson.build
+++ b/meson.build
@@ -593,7 +593,7 @@
config_manfile = configuration_data()
config_manfile.set('VERSION', version)
config_manfile.set('MAN_DATE', run_command('util/getversion.sh', '--man-date', check : true).stdout().strip())
-foreach man : [ 'flashprog.8', 'flashprog-config.8' ]
+foreach man : [ 'flashprog.8', 'flashprog-config.8', 'flashprog-write-protect.8' ]
configure_file(
input : man + '.tmpl',
output : man,
@@ -609,6 +609,7 @@
files(
'cli.c',
'cli_config.c',
+ 'cli_wp.c',
'cli_classic.c',
'cli_common.c',
'cli_output.c',