cli: Add new `config' CLI for status/config registers

Add a new CLI mode to query and update status and configuration
registers of SPI NOR chips.  Programmer initialization and chip
initialization works the same as with the classic CLI (`-p' and
`-c' options). There are two commands `get' and `set' where the
former is implied if no command is given. For a start, only the
`quad-enable' bit can be accessed  (for chips that advertise it
in the database).

The `--temporary' option  allows to use a volatile write status
register command if the flash chip supports it. So changes made
with this option will not be written to flash and are lost when
the chip is reset.

For instance, the quad-enable bit can then be queried like this

  $ flashprog config get -p ch341a_spi quad-enable

or written with

  $ flashprog config set -p ch341a_spi quad-enable 1

or

  $ flashprog config set -p ch341a_spi --temporary quad-enable 1

Change-Id: I6b9d26c67e6ad65be5df367d2db7942bb98f27ac
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/195
diff --git a/Makefile b/Makefile
index a75c280..65b808b 100644
--- a/Makefile
+++ b/Makefile
@@ -401,7 +401,7 @@
 ###############################################################################
 # Frontend related stuff.
 
-CLI_OBJS = cli.o cli_classic.o cli_output.o cli_common.o print.o
+CLI_OBJS = cli.o cli_config.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,9 +925,9 @@
 endif
 
 OBJS = $(CHIP_OBJS) $(PROGRAMMER_OBJS) $(LIB_OBJS)
+MANS = $(PROGRAM).8 $(PROGRAM)-config.8
 
-
-all: $(PROGRAM)$(EXEC_SUFFIX) $(PROGRAM).8
+all: $(PROGRAM)$(EXEC_SUFFIX) $(MANS)
 ifeq ($(ARCH), x86)
 	@+$(MAKE) -C util/ich_descriptors_tool/ HOST_OS=$(HOST_OS) TARGET_OS=$(TARGET_OS)
 endif
@@ -1013,12 +1013,12 @@
 	$(AR) rcs $@ $^
 	$(RANLIB) $@
 
-$(PROGRAM).8.html: $(PROGRAM).8
-	@groff -mandoc -Thtml $< >$@
+%.8.html: %.8
+	@groff -mandoc -Thtml $< | sed 's/href="man:\([^(]*\)(\([^)]*\))"/href="\1.\2.html"/' >$@
 
-$(PROGRAM).8: $(PROGRAM).8.tmpl
+%.8: %.8.tmpl
 	@# Add the man page change date and version to the man page
-	@sed -e 's#.TH FLASHPROG 8 .*#.TH FLASHPROG 8 "$(MAN_DATE)" "flashprog-$(VERSION)" "$(MAN_DATE)"#' <$< >$@
+	@sed -e 's#.TH \(FLASHPROG[^ ]*\) 8 .*#.TH \1 8 "$(MAN_DATE)" "\L\1-$(VERSION)" "$(MAN_DATE)"#' <$< >$@
 
 strip: $(PROGRAM)$(EXEC_SUFFIX)
 	$(STRIP) $(STRIP_ARGS) $(PROGRAM)$(EXEC_SUFFIX)
@@ -1028,14 +1028,14 @@
 # We don't use EXEC_SUFFIX here because we want to clean everything.
 clean:
 	rm -f $(PROGRAM) $(PROGRAM).exe libflashprog.a $(filter-out Makefile.d, $(wildcard *.d *.o platform/*.d platform/*.o)) \
-		$(PROGRAM).8 $(PROGRAM).8.html $(BUILD_DETAILS_FILE)
+		$(MANS) $(MANS:.8=.8.html) $(BUILD_DETAILS_FILE)
 	@+$(MAKE) -C util/ich_descriptors_tool/ clean
 
-install: $(PROGRAM)$(EXEC_SUFFIX) $(PROGRAM).8
+install: $(PROGRAM)$(EXEC_SUFFIX) $(MANS)
 	mkdir -p $(DESTDIR)$(PREFIX)/sbin
 	mkdir -p $(DESTDIR)$(MANDIR)/man8
 	$(INSTALL) -m 0755 $(PROGRAM)$(EXEC_SUFFIX) $(DESTDIR)$(PREFIX)/sbin
-	$(INSTALL) -m 0644 $(PROGRAM).8 $(DESTDIR)$(MANDIR)/man8
+	$(INSTALL) -m 0644 $(MANS) $(DESTDIR)$(MANDIR)/man8
 
 libinstall: libflashprog.a include/libflashprog.h
 	mkdir -p $(DESTDIR)$(PREFIX)/lib
@@ -1063,7 +1063,7 @@
 RELEASENAME ?= flashprog-$(shell echo "$(VERSION)" | sed -e 's/ /_/')
 
 _export: EXPORT_VERSIONINFO := $(EXPORTDIR)/$(RELEASENAME)/versioninfo.inc
-_export: $(PROGRAM).8
+_export: $(MANS)
 	@rm -rf "$(EXPORTDIR)/$(RELEASENAME)"
 	@mkdir -p "$(EXPORTDIR)/$(RELEASENAME)"
 	@git archive HEAD | tar -x -C "$(EXPORTDIR)/$(RELEASENAME)"
diff --git a/cli.c b/cli.c
index 7bf94cc..f1d483a 100644
--- a/cli.c
+++ b/cli.c
@@ -26,6 +26,8 @@
 	int (*main)(int argc, char *argv[]);
 } commands[] = {
 	{ "prog",		flashprog_classic_main },
+	{ "cfg",		flashprog_config_main },
+	{ "config",		flashprog_config_main },
 };
 
 static void usage(const char *const name)
@@ -34,6 +36,7 @@
 	fprintf(stderr, "\nWhere <command> can be\n\n"
 			" prog                     Standard memory operations\n"
 			"                          (read/erase/write/verify)\n"
+			" cfg | config             Status/config register operations\n"
 			"\n"
 			"The default is 'prog'. See `%s <command> --help`\n"
 			"for further instructions.\n\n", name);
diff --git a/cli_config.c b/cli_config.c
new file mode 100644
index 0000000..3ffa048
--- /dev/null
+++ b/cli_config.c
@@ -0,0 +1,264 @@
+/*
+ * 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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include "libflashprog.h"
+#include "chipdrivers.h"
+#include "flash.h"
+#include "cli.h"
+
+enum settings {
+	QUAD_ENABLE,
+};
+
+static const struct reg_bit_info *get_bit_info(
+		const struct flashctx *flash, enum settings setting)
+{
+	switch (setting) {
+	case QUAD_ENABLE:
+		return &flash->chip->reg_bits.qe;
+	default:
+		return NULL;
+	}
+}
+
+static int config_get(const struct flashctx *flash, enum settings setting)
+{
+	const struct reg_bit_info *const bit = get_bit_info(flash, setting);
+	uint8_t reg_val;
+
+	if (!bit)
+		return 1;
+
+	const int ret = spi_read_register(flash, bit->reg, &reg_val);
+	if (ret)
+		return 1;
+
+	printf("%u\n", reg_val >> bit->bit_index & 1);
+	return 0;
+}
+
+static int config_set(const struct flashctx *flash, enum settings setting, unsigned int value)
+{
+	const struct reg_bit_info *const bit = get_bit_info(flash, setting);
+	uint8_t reg_val;
+	int ret;
+
+	if (!bit)
+		return 1;
+
+	ret = spi_read_register(flash, bit->reg, &reg_val);
+	if (ret)
+		return 1;
+
+	reg_val &= ~(1 << bit->bit_index);
+	reg_val |= (value & 1) << bit->bit_index;
+
+	ret = spi_write_register(flash, bit->reg, reg_val, default_wrsr_target(flash));
+	if (ret)
+		return 1;
+
+	return 0;
+}
+
+static void usage(const char *const name, const char *const msg)
+{
+	if (msg)
+		fprintf(stderr, "\nError: %s\n", msg);
+
+	fprintf(stderr, "\nUsage:"
+			"\t%s [get] <options> <setting>\n"
+			"\t%s  set  <options> [--temporary] <setting> <value>\n",
+			name, name);
+	print_generic_options();
+	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"
+			"\nBy default, the setting is queried (`get').\n"
+			"\n");
+	exit(1);
+}
+
+static int parse_setting(const char *const setting)
+{
+	if (!strcmp(setting, "qe") ||
+	    !strcmp(setting, "quad-enable"))
+		return QUAD_ENABLE;
+	return -1;
+}
+
+static int parse_value(const char *const value)
+{
+	if (!strcmp(value, "true"))
+		return 1;
+	if (!strcmp(value, "false"))
+		return 0;
+
+	char *endptr;
+	const unsigned long i = strtoul(value, &endptr, 0);
+	if (value[0] && !endptr[0] && i <= INT_MAX)
+		return i;
+
+	return -1;
+}
+
+int flashprog_config_main(int argc, char *argv[])
+{
+	static const char optstring[] = "+p:c:Vo:h";
+	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'},
+		{"get",			0, NULL, OPTION_CONFIG_GET},
+		{"set",			0, NULL, OPTION_CONFIG_SET},
+		{"temporary",		0, NULL, OPTION_CONFIG_VOLATILE},
+		{NULL,			0, NULL, 0},
+	};
+	static const struct opt_command cmd_options[] = {
+		{"get",		OPTION_CONFIG_GET},
+		{"set",		OPTION_CONFIG_SET},
+		{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 };
+	bool get = false, set = false, volat1le = false;
+
+	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_CONFIG_GET:
+			get = true;
+			++ops;
+			break;
+		case OPTION_CONFIG_SET:
+			set = true;
+			++ops;
+			break;
+		case OPTION_CONFIG_VOLATILE:
+			volat1le = true;
+			break;
+		case '?':
+		case 'h':
+			usage(argv[0], NULL);
+			break;
+		}
+	}
+
+	if (!ops) {
+		get = true;
+		++ops;
+	}
+
+	if (ops > 1)
+		usage(argv[0], "Only one operation may be specified.");
+
+	if (!set && volat1le)
+		usage(argv[0], "`--temporary' may only be specified for `set'.");
+	if (get && optind != argc - 1)
+		usage(argv[0], "`get' requires exactly one argument.");
+	if (set && optind != argc - 2)
+		usage(argv[0], "`set' requires exactly two arguments.");
+
+	if (!flash_args.prog_name)
+		usage(argv[0], "No programmer specified.");
+
+	const int setting = parse_setting(argv[optind]);
+	if (setting < 0) {
+		fprintf(stderr, "\nError: Unknown <setting> argument `%s'.\n", argv[optind]);
+		usage(argv[0], NULL);
+	}
+	int value = 0;
+	if (set) {
+		value = parse_value(argv[optind + 1]);
+		if (value < 0) {
+			fprintf(stderr, "\nError: Cannot parse value `%s'.\n", argv[optind + 1]);
+			usage(argv[0], NULL);
+		}
+	}
+
+	struct flashprog_programmer *prog;
+	struct flashprog_flashctx *flash;
+	ret = 1;
+
+	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;
+	if (flashprog_flash_probe(&flash, prog, flash_args.chip)) {
+		fprintf(stderr, "No EEPROM/flash device found.\n");
+		goto shutdown_ret;
+	}
+
+	if (flash->chip->bustype != BUS_SPI || flash->chip->spi_cmd_set != SPI25) {
+		fprintf(stderr, "Only SPI25 flash chips are supported.\n");
+		goto shutdown_ret;
+	}
+
+	flashprog_flag_set(flash, FLASHPROG_FLAG_NON_VOLATILE_WRSR, set && !volat1le);
+
+	if (get)
+		ret = config_get(flash, setting);
+
+	if (set)
+		ret = config_set(flash, setting, value);
+
+	flashprog_flash_release(flash);
+shutdown_ret:
+	flashprog_programmer_shutdown(prog);
+free_ret:
+	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-config.8.tmpl b/flashprog-config.8.tmpl
new file mode 100644
index 0000000..9fcb1ad
--- /dev/null
+++ b/flashprog-config.8.tmpl
@@ -0,0 +1,221 @@
+.\" 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-CONFIG 8 "@MAN_DATE@" "flashprog-config-@VERSION@" "@MAN_DATE@"
+.SH NAME
+flashprog-config \- read and write status and configuration registers of flash chips
+.SH SYNOPSIS
+.I flashprog config \fR[\fIget\fR] <options> <setting>
+.br
+.I flashprog config \ set \ \fR<options> [\fB\-\-temporary\fR] <setting> <value>
+.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
+
+.SH DESCRIPTION
+.B flashprog-config
+is a utility for reading and writing status and configuration register
+bits of flash chips. Currently, it supports only SPI NOR chips.
+
+.SH OPERATIONS
+You can specify one of
+.BR get " or " set ", "
+or no operation which defaults to reading a setting.
+.PP
+.BR get " <setting>"
+.RS 4
+Read and print the value of the given setting. See
+.BR SETTINGS " below."
+.RE
+.PP
+.BR set " [" \-\-temporary "] <setting> <value>"
+.RS 4
+Write the given value to the setting. See
+.BR SETTINGS " and " VALUES " below."
+.sp
+When the
+.B \-\-temporary
+option is provided, 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 SETTINGS
+.PP
+.B qe, quad-enable
+.RS 4
+SPI NOR flash chips often support muxing some of their pins (usually
+/WP and /HOLD) with additional i/o lines. This enables them to transfer
+four bits at once when the
+.B quad-enable
+bit is set.
+.RE
+
+.SH VALUES
+.PP
+.BR false ", " true
+.RS 4
+The values
+.BR false " and " true
+will be converted to
+.BR 0 " and " 1
+respectively.
+.RE
+.PP
+.RB "natural numbers: " 0 ", " 1 ", ..."
+.RS 4
+When natural numbers are given, the least-significant bits of their
+binary representation will be written to a setting.
+.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
+.BR \-\-temporary
+.RS 4
+Try to use a volatile write status register command. See
+.BR set " under " OPERATIONS " above."
+.RE
+
+.SH EXAMPLES
+To read the
+.B quad-enable
+setting of the internal BIOS flash:
+.sp
+.RS 2
+.B flashprog config -p internal quad-enable
+.sp
+.RE
+or
+.sp
+.RS 2
+.B flashprog config get -p internal quad-enable
+.sp
+.RE
+To temporarily set the
+.B quad-enable
+bit of a chip connected to an FT4222H:
+.sp
+.RS 2
+.B flashprog config set -p ft4222_spi --temporary quad-enable 1
+.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
+.MR 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 54daef1..d9a59f9 100644
--- a/flashprog.8.tmpl
+++ b/flashprog.8.tmpl
@@ -46,11 +46,12 @@
 .SH SYNOPSIS
 Flashprog supports multiple command modes:
 .sp
-.B flashprog \fR([\fBprog\fR])
+.B flashprog \fR([\fBprog\fR]|\fBconfig\fR|\fBcfg\fR)
 .sp
 With
 .B prog
-being the default and described in this manual.
+being the default and described in this manual. For the other commands, see
+.MR flashprog-config 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>]
@@ -1735,3 +1736,6 @@
 .MTOB "uwe@hermann-uwe.de" "Uwe Hermann" ,
 Carl-Daniel Hailfinger, Stefan Tauner and others.
 It is licensed under the terms of the GNU GPL (version 2 or later).
+
+.SH SEE ALSO
+.MR flashprog-config 8
diff --git a/include/cli.h b/include/cli.h
index 06cd517..69b0146 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -32,6 +32,9 @@
 	OPTION_FLASH_NAME,
 	OPTION_FLASH_SIZE,
 	OPTION_PROGRESS,
+	OPTION_CONFIG_GET,
+	OPTION_CONFIG_SET,
+	OPTION_CONFIG_VOLATILE,
 };
 
 struct log_args {
@@ -63,6 +66,7 @@
 int cli_init(void);
 
 int flashprog_classic_main(int argc, char *argv[]);
+int flashprog_config_main(int argc, char *argv[]);
 
 extern enum flashprog_log_level verbose_screen;
 extern enum flashprog_log_level verbose_logfile;
diff --git a/meson.build b/meson.build
index f308ec6..7a3d35c 100644
--- a/meson.build
+++ b/meson.build
@@ -593,19 +593,22 @@
 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())
-configure_file(
-  input : 'flashprog.8.tmpl',
-  output : 'flashprog.8',
-  configuration : config_manfile,
-  install: true,
-  install_dir: join_paths(get_option('mandir'), 'man8'),
-)
+foreach man : [ 'flashprog.8', 'flashprog-config.8' ]
+  configure_file(
+    input : man + '.tmpl',
+    output : man,
+    configuration : config_manfile,
+    install: true,
+    install_dir: join_paths(get_option('mandir'), 'man8'),
+  )
+endforeach
 
 if get_option('classic_cli').auto() or get_option('classic_cli').enabled()
   executable(
     'flashprog',
     files(
       'cli.c',
+      'cli_config.c',
       'cli_classic.c',
       'cli_common.c',
       'cli_output.c',