summaryrefslogtreecommitdiff
path: root/0001-Update-carl9170-to-latest-upstream.patch
diff options
context:
space:
mode:
Diffstat (limited to '0001-Update-carl9170-to-latest-upstream.patch')
-rw-r--r--0001-Update-carl9170-to-latest-upstream.patch4982
1 files changed, 4982 insertions, 0 deletions
diff --git a/0001-Update-carl9170-to-latest-upstream.patch b/0001-Update-carl9170-to-latest-upstream.patch
new file mode 100644
index 0000000..e56707e
--- /dev/null
+++ b/0001-Update-carl9170-to-latest-upstream.patch
@@ -0,0 +1,4982 @@
+From c164bf7f87f9081fee7e1a186dd7a87a9a020b9e Mon Sep 17 00:00:00 2001
+From: Jason Self <j@jxself.org>
+Date: Thu, 4 Jul 2019 15:12:33 -0700
+Subject: [PATCH 1/8] Update carl9170 to latest upstream
+
+Based on commit 001384147050b9cd9daadb4d3115cc0f13f5b319
+Dated May 5 2019.
+---
+ WHENCE | 4 +-
+ carl9170fw/carlfw/include/wl.h | 2 +-
+ carl9170fw/carlfw/src/hostif.c | 8 +-
+ carl9170fw/carlfw/src/printf.c | 0
+ carl9170fw/carlfw/src/wlantx.c | 5 +-
+ carl9170fw/config/CMakeLists.txt | 6 +-
+ carl9170fw/config/conf.c | 210 ++++++----
+ carl9170fw/config/confdata.c | 329 +++++++++------
+ carl9170fw/config/expr.c | 206 +++++++---
+ carl9170fw/config/expr.h | 105 ++++-
+ carl9170fw/config/kconf_id.c | 54 ---
+ carl9170fw/config/lkc.h | 50 +--
+ carl9170fw/config/lkc_proto.h | 19 +-
+ carl9170fw/config/menu.c | 259 +++++++++---
+ carl9170fw/config/preprocess.c | 573 +++++++++++++++++++++++++++
+ carl9170fw/config/symbol.c | 255 ++++--------
+ carl9170fw/config/util.c | 86 ++--
+ carl9170fw/config/zconf.l | 336 ++++++++++------
+ carl9170fw/config/zconf.y | 403 +++++++++----------
+ carl9170fw/include/linux/ieee80211.h | 24 +-
+ carl9170fw/include/shared/wlan.h | 2 +-
+ carl9170fw/toolchain/Makefile | 8 +-
+ carl9170fw/toolchain/SHA256SUMS | 18 +-
+ 23 files changed, 1942 insertions(+), 1020 deletions(-)
+ mode change 100755 => 100644 carl9170fw/carlfw/src/printf.c
+ delete mode 100644 carl9170fw/config/kconf_id.c
+ create mode 100644 carl9170fw/config/preprocess.c
+
+diff --git a/WHENCE b/WHENCE
+index dd8ec20..2932155 100644
+--- a/WHENCE
++++ b/WHENCE
+@@ -142,8 +142,8 @@ From https://git.kernel.org/pub/scm/utils/cis-tools/cis-tools.git
+
+ Driver: carl9170 -- Atheros AR9170 802.11 draft-n USB driver
+
+-Version: Based on commit 370b7919114a02149088c482c8709cafb9bf7478
+-dated May 2 2018.
++Version: Based on commit 001384147050b9cd9daadb4d3115cc0f13f5b319
++dated May 5 2019.
+
+ Licence: GPLv2 or later.
+
+diff --git a/carl9170fw/carlfw/include/wl.h b/carl9170fw/carlfw/include/wl.h
+index 8499ca2..5566be4 100644
+--- a/carl9170fw/carlfw/include/wl.h
++++ b/carl9170fw/carlfw/include/wl.h
+@@ -237,7 +237,7 @@ static inline __inline void unhide_super(struct dma_desc *desc)
+ desc->totalLen += sizeof(struct carl9170_tx_superdesc);
+ }
+
+-static inline __inline __hot void read_tsf(uint32_t *tsf)
++static inline __inline __hot void read_tsf(uint32_t tsf[static 2])
+ {
+ /*
+ * "According to the [hardware] documentation:
+diff --git a/carl9170fw/carlfw/src/hostif.c b/carl9170fw/carlfw/src/hostif.c
+index 73e89c7..06726db 100644
+--- a/carl9170fw/carlfw/src/hostif.c
++++ b/carl9170fw/carlfw/src/hostif.c
+@@ -213,10 +213,14 @@ void handle_cmd(struct carl9170_rsp *resp)
+ fw.reboot = 1;
+ break;
+
+- case CARL9170_CMD_READ_TSF:
++ case CARL9170_CMD_READ_TSF: {
++ uint32_t tmptsf[2];
++
++ read_tsf(tmptsf);
+ resp->hdr.len = 8;
+- read_tsf((uint32_t *)resp->tsf.tsf);
++ memcpy(resp->tsf.tsf, tmptsf, sizeof(tmptsf));
+ break;
++ }
+
+ case CARL9170_CMD_RX_FILTER:
+ resp->hdr.len = 0;
+diff --git a/carl9170fw/carlfw/src/printf.c b/carl9170fw/carlfw/src/printf.c
+old mode 100755
+new mode 100644
+diff --git a/carl9170fw/carlfw/src/wlantx.c b/carl9170fw/carlfw/src/wlantx.c
+index 474c040..a8d0952 100644
+--- a/carl9170fw/carlfw/src/wlantx.c
++++ b/carl9170fw/carlfw/src/wlantx.c
+@@ -260,7 +260,7 @@ static void __wlan_tx(struct dma_desc *desc)
+
+ if (unlikely(super->s.fill_in_tsf)) {
+ struct ieee80211_mgmt *mgmt = (void *) &super->f.data.i3e;
+- uint32_t *tsf = (uint32_t *) &mgmt->u.probe_resp.timestamp;
++ uint32_t tmptsf[2];
+
+ /*
+ * Truth be told: this is a hack.
+@@ -272,7 +272,8 @@ static void __wlan_tx(struct dma_desc *desc)
+ * (even, if it's got an accurate atomic clock source).
+ */
+
+- read_tsf(tsf);
++ read_tsf(tmptsf);
++ memcpy(&mgmt->u.probe_resp.timestamp, tmptsf, sizeof(tmptsf));
+ }
+
+ wlan_tx_ampdu(super);
+diff --git a/carl9170fw/config/CMakeLists.txt b/carl9170fw/config/CMakeLists.txt
+index 0a96a82..23e7218 100644
+--- a/carl9170fw/config/CMakeLists.txt
++++ b/carl9170fw/config/CMakeLists.txt
+@@ -11,13 +11,13 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../include/generated")
+
+-BISON_TARGET(zconf zconf.y zconf.tab.c COMPILE_FLAGS "-l -b zconf -p zconf -t")
+-FLEX_TARGET(zconfscan zconf.l zconf.lex.c COMPILE_FLAGS "-Pzconf -L")
++BISON_TARGET(zconf zconf.y zconf.tab.c COMPILE_FLAGS "-l -b zconf -p yy -t")
++FLEX_TARGET(zconfscan zconf.l zconf.lex.c COMPILE_FLAGS "-Pyy -L")
+
+ SET(zconf_deps ${FLEX_zconfscan_OUTPUTS})
+ SET_SOURCE_FILES_PROPERTIES(${BISON_zconf_OUTPUTS}
+ PROPERTIES OBJECT_DEPENDS "${zconf_deps}")
+
+-set(conf_src conf.c ${BISON_zconf_OUTPUTS})
++set(conf_src conf.c symbol.c confdata.c expr.c preprocess.c ${BISON_zconf_OUTPUTS} ${FLEX_zconfscan_OUTPUTS})
+
+ add_executable(conf ${conf_src})
+diff --git a/carl9170fw/config/conf.c b/carl9170fw/config/conf.c
+index 6be6143..2949b7d 100644
+--- a/carl9170fw/config/conf.c
++++ b/carl9170fw/config/conf.c
+@@ -1,9 +1,8 @@
++// SPDX-License-Identifier: GPL-2.0
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+-#include <locale.h>
+ #include <ctype.h>
+ #include <limits.h>
+ #include <stdio.h>
+@@ -20,10 +19,10 @@
+
+ static void conf(struct menu *menu);
+ static void check_conf(struct menu *menu);
+-static void xfgets(char *str, int size, FILE *in);
+
+ enum input_mode {
+ oldaskconfig,
++ syncconfig,
+ oldconfig,
+ allnoconfig,
+ allyesconfig,
+@@ -33,12 +32,13 @@ enum input_mode {
+ defconfig,
+ savedefconfig,
+ listnewconfig,
+- oldnoconfig,
+-} input_mode = oldaskconfig;
++ olddefconfig,
++};
++static enum input_mode input_mode = oldaskconfig;
+
+ static int indent = 1;
+ static int tty_stdio;
+-static int valid_stdin = 1;
++static int sync_kconfig;
+ static int conf_cnt;
+ static char line[PATH_MAX];
+ static struct menu *rootEntry;
+@@ -70,14 +70,14 @@ static void strip(char *str)
+ *p-- = 0;
+ }
+
+-static void check_stdin(void)
++/* Helper function to facilitate fgets() by Jean Sacren. */
++static void xfgets(char *str, int size, FILE *in)
+ {
+- if (!valid_stdin) {
+- printf(_("aborted!\n\n"));
+- printf(_("Console input/output is redirected. "));
+- printf(_("Run 'make config' to update configuration.\n\n"));
+- exit(1);
+- }
++ if (!fgets(str, size, in))
++ fprintf(stderr, "\nError in reading or end of file.\n");
++
++ if (!tty_stdio)
++ printf("%s", str);
+ }
+
+ static int conf_askvalue(struct symbol *sym, const char *def)
+@@ -85,7 +85,7 @@ static int conf_askvalue(struct symbol *sym, const char *def)
+ enum symbol_type type = sym_get_type(sym);
+
+ if (!sym_has_value(sym))
+- printf(_("(NEW) "));
++ printf("(NEW) ");
+
+ line[0] = '\n';
+ line[1] = 0;
+@@ -99,17 +99,15 @@ static int conf_askvalue(struct symbol *sym, const char *def)
+
+ switch (input_mode) {
+ case oldconfig:
++ case syncconfig:
+ if (sym_has_value(sym)) {
+ printf("%s\n", def);
+ return 0;
+ }
+- check_stdin();
+ /* fall through */
+ case oldaskconfig:
+ fflush(stdout);
+ xfgets(line, sizeof(line), stdin);
+- if (!tty_stdio)
+- printf("\n");
+ return 1;
+ default:
+ break;
+@@ -134,7 +132,7 @@ static int conf_string(struct menu *menu)
+ const char *def;
+
+ while (1) {
+- printf("%*s%s ", indent - 1, "", _(menu->prompt->text));
++ printf("%*s%s ", indent - 1, "", menu->prompt->text);
+ printf("(%s) ", sym->name);
+ def = sym_get_string_value(sym);
+ if (sym_get_string_value(sym))
+@@ -167,7 +165,7 @@ static int conf_sym(struct menu *menu)
+ tristate oldval, newval;
+
+ while (1) {
+- printf("%*s%s ", indent - 1, "", _(menu->prompt->text));
++ printf("%*s%s ", indent - 1, "", menu->prompt->text);
+ if (sym->name)
+ printf("(%s) ", sym->name);
+ putchar('[');
+@@ -189,9 +187,7 @@ static int conf_sym(struct menu *menu)
+ printf("/m");
+ if (oldval != yes && sym_tristate_within_range(sym, yes))
+ printf("/y");
+- if (menu_has_help(menu))
+- printf("/?");
+- printf("] ");
++ printf("/?] ");
+ if (!conf_askvalue(sym, sym_get_string_value(sym)))
+ return 0;
+ strip(line);
+@@ -254,7 +250,7 @@ static int conf_choice(struct menu *menu)
+ case no:
+ return 1;
+ case mod:
+- printf("%*s%s\n", indent - 1, "", _(menu_get_prompt(menu)));
++ printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu));
+ return 0;
+ case yes:
+ break;
+@@ -264,7 +260,7 @@ static int conf_choice(struct menu *menu)
+ while (1) {
+ int cnt, def;
+
+- printf("%*s%s\n", indent - 1, "", _(menu_get_prompt(menu)));
++ printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu));
+ def_sym = sym_get_choice_value(sym);
+ cnt = def = 0;
+ line[0] = 0;
+@@ -272,7 +268,7 @@ static int conf_choice(struct menu *menu)
+ if (!menu_is_visible(child))
+ continue;
+ if (!child->sym) {
+- printf("%*c %s\n", indent, '*', _(menu_get_prompt(child)));
++ printf("%*c %s\n", indent, '*', menu_get_prompt(child));
+ continue;
+ }
+ cnt++;
+@@ -281,30 +277,27 @@ static int conf_choice(struct menu *menu)
+ printf("%*c", indent, '>');
+ } else
+ printf("%*c", indent, ' ');
+- printf(" %d. %s", cnt, _(menu_get_prompt(child)));
++ printf(" %d. %s", cnt, menu_get_prompt(child));
+ if (child->sym->name)
+ printf(" (%s)", child->sym->name);
+ if (!sym_has_value(child->sym))
+- printf(_(" (NEW)"));
++ printf(" (NEW)");
+ printf("\n");
+ }
+- printf(_("%*schoice"), indent - 1, "");
++ printf("%*schoice", indent - 1, "");
+ if (cnt == 1) {
+ printf("[1]: 1\n");
+ goto conf_childs;
+ }
+- printf("[1-%d", cnt);
+- if (menu_has_help(menu))
+- printf("?");
+- printf("]: ");
++ printf("[1-%d?]: ", cnt);
+ switch (input_mode) {
+ case oldconfig:
++ case syncconfig:
+ if (!is_new) {
+ cnt = def;
+ printf("%d\n", cnt);
+ break;
+ }
+- check_stdin();
+ /* fall through */
+ case oldaskconfig:
+ fflush(stdout);
+@@ -364,9 +357,11 @@ static void conf(struct menu *menu)
+
+ switch (prop->type) {
+ case P_MENU:
+- if ((input_mode == listnewconfig ||
+- input_mode == oldnoconfig) &&
+- rootEntry != menu) {
++ /*
++ * Except in oldaskconfig mode, we show only menus that
++ * contain new symbols.
++ */
++ if (input_mode != oldaskconfig && rootEntry != menu) {
+ check_conf(menu);
+ return;
+ }
+@@ -376,7 +371,7 @@ static void conf(struct menu *menu)
+ if (prompt)
+ printf("%*c\n%*c %s\n%*c\n",
+ indent, '*',
+- indent, '*', _(prompt),
++ indent, '*', prompt,
+ indent, '*');
+ default:
+ ;
+@@ -426,12 +421,22 @@ static void check_conf(struct menu *menu)
+ if (sym_is_changable(sym) ||
+ (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) {
+ if (input_mode == listnewconfig) {
+- if (sym->name && !sym_is_choice_value(sym)) {
+- printf("%s%s\n", CONFIG_, sym->name);
++ if (sym->name) {
++ const char *str;
++
++ if (sym->type == S_STRING) {
++ str = sym_get_string_value(sym);
++ str = sym_escape_string_value(str);
++ printf("%s%s=%s\n", CONFIG_, sym->name, str);
++ free((void *)str);
++ } else {
++ str = sym_get_string_value(sym);
++ printf("%s%s=%s\n", CONFIG_, sym->name, str);
++ }
+ }
+- } else if (input_mode != oldnoconfig) {
++ } else {
+ if (!conf_cnt++)
+- printf(_("*\n* Restart config...\n*\n"));
++ printf("*\n* Restart config...\n*\n");
+ rootEntry = menu_get_parent_menu(menu);
+ conf(rootEntry);
+ }
+@@ -443,8 +448,9 @@ static void check_conf(struct menu *menu)
+ }
+
+ static struct option long_opts[] = {
+- {"askconfig", no_argument, NULL, oldaskconfig},
+- {"config", no_argument, NULL, oldconfig},
++ {"oldaskconfig", no_argument, NULL, oldaskconfig},
++ {"oldconfig", no_argument, NULL, oldconfig},
++ {"syncconfig", no_argument, NULL, syncconfig},
+ {"defconfig", optional_argument, NULL, defconfig},
+ {"savedefconfig", required_argument, NULL, savedefconfig},
+ {"allnoconfig", no_argument, NULL, allnoconfig},
+@@ -453,7 +459,7 @@ static struct option long_opts[] = {
+ {"alldefconfig", no_argument, NULL, alldefconfig},
+ {"randconfig", no_argument, NULL, randconfig},
+ {"listnewconfig", no_argument, NULL, listnewconfig},
+- {"noconfig", no_argument, NULL, oldnoconfig},
++ {"olddefconfig", no_argument, NULL, olddefconfig},
+ {NULL, 0, NULL, 0}
+ };
+
+@@ -463,10 +469,11 @@ static void conf_usage(const char *progname)
+ printf("Usage: %s [-s] [option] <kconfig-file>\n", progname);
+ printf("[option] is _one_ of the following:\n");
+ printf(" --listnewconfig List new options\n");
+- printf(" --askconfig Start a new configuration using a line-oriented program\n");
+- printf(" --config Update a configuration using a provided .config as base\n");
+- printf(" --silentconfig Same as config, but quietly, additionally update deps\n");
+- printf(" --noconfig Same as silentconfig but set new symbols to no\n");
++ printf(" --oldaskconfig Start a new configuration using a line-oriented program\n");
++ printf(" --oldconfig Update a configuration using a provided .config as base\n");
++ printf(" --syncconfig Similar to oldconfig but generates configuration in\n"
++ " include/{generated/,config/}\n");
++ printf(" --olddefconfig Same as oldconfig but sets new symbols to their default value\n");
+ printf(" --defconfig <file> New config with default defined in <file>\n");
+ printf(" --savedefconfig <file> Save the minimal current configuration to <file>\n");
+ printf(" --allnoconfig New config where all options are answered with no\n");
+@@ -482,12 +489,9 @@ int main(int ac, char **av)
+ int opt;
+ const char *name, *defconfig_file = NULL /* gcc uninit */;
+ struct stat tmpstat;
++ int no_conf_write = 0;
+
+- setlocale(LC_ALL, "");
+- bindtextdomain(PACKAGE, LOCALEDIR);
+- textdomain(PACKAGE);
+-
+- tty_stdio = isatty(0) && isatty(1) && isatty(2);
++ tty_stdio = isatty(0) && isatty(1);
+
+ while ((opt = getopt_long(ac, av, "s", long_opts, NULL)) != -1) {
+ if (opt == 's') {
+@@ -496,6 +500,14 @@ int main(int ac, char **av)
+ }
+ input_mode = (enum input_mode)opt;
+ switch (opt) {
++ case syncconfig:
++ /*
++ * syncconfig is invoked during the build stage.
++ * Suppress distracting "configuration written to ..."
++ */
++ conf_set_message_callback(NULL);
++ sync_kconfig = 1;
++ break;
+ case defconfig:
+ case savedefconfig:
+ defconfig_file = optarg;
+@@ -532,7 +544,7 @@ int main(int ac, char **av)
+ case allmodconfig:
+ case alldefconfig:
+ case listnewconfig:
+- case oldnoconfig:
++ case olddefconfig:
+ break;
+ case '?':
+ conf_usage(progname);
+@@ -541,30 +553,45 @@ int main(int ac, char **av)
+ }
+ }
+ if (ac == optind) {
+- printf(_("%s: Kconfig file missing\n"), av[0]);
++ fprintf(stderr, "%s: Kconfig file missing\n", av[0]);
+ conf_usage(progname);
+ exit(1);
+ }
+ name = av[optind];
+ conf_parse(name);
+ //zconfdump(stdout);
++ if (sync_kconfig) {
++ name = conf_get_configname();
++ if (stat(name, &tmpstat)) {
++ fprintf(stderr, "***\n"
++ "*** Configuration file \"%s\" not found!\n"
++ "***\n"
++ "*** Please run some configurator (e.g. \"make oldconfig\" or\n"
++ "*** \"make menuconfig\" or \"make xconfig\").\n"
++ "***\n", name);
++ exit(1);
++ }
++ }
+
+ switch (input_mode) {
+ case defconfig:
+ if (!defconfig_file)
+ defconfig_file = conf_get_default_confname();
+ if (conf_read(defconfig_file)) {
+- printf(_("***\n"
+- "*** Can't find default configuration \"%s\"!\n"
+- "***\n"), defconfig_file);
++ fprintf(stderr,
++ "***\n"
++ "*** Can't find default configuration \"%s\"!\n"
++ "***\n",
++ defconfig_file);
+ exit(1);
+ }
+ break;
+ case savedefconfig:
++ case syncconfig:
+ case oldaskconfig:
+ case oldconfig:
+ case listnewconfig:
+- case oldnoconfig:
++ case olddefconfig:
+ conf_read(NULL);
+ break;
+ case allnoconfig:
+@@ -578,7 +605,7 @@ int main(int ac, char **av)
+ if ((strcmp(name, "") != 0) && (strcmp(name, "1") != 0)) {
+ if (conf_read_simple(name, S_DEF_USER)) {
+ fprintf(stderr,
+- _("*** Can't read seed configuration \"%s\"!\n"),
++ "*** Can't read seed configuration \"%s\"!\n",
+ name);
+ exit(1);
+ }
+@@ -595,7 +622,7 @@ int main(int ac, char **av)
+ if (conf_read_simple(name, S_DEF_USER) &&
+ conf_read_simple("all.config", S_DEF_USER)) {
+ fprintf(stderr,
+- _("*** KCONFIG_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n"),
++ "*** KCONFIG_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n",
+ name);
+ exit(1);
+ }
+@@ -604,7 +631,17 @@ int main(int ac, char **av)
+ break;
+ }
+
+- valid_stdin = tty_stdio;
++ if (sync_kconfig) {
++ name = getenv("KCONFIG_NOSILENTUPDATE");
++ if (name && *name) {
++ if (conf_get_changed()) {
++ fprintf(stderr,
++ "\n*** The configuration requires explicit update.\n\n");
++ return 1;
++ }
++ no_conf_write = 1;
++ }
++ }
+
+ switch (input_mode) {
+ case allnoconfig:
+@@ -635,44 +672,51 @@ int main(int ac, char **av)
+ /* fall through */
+ case oldconfig:
+ case listnewconfig:
+- case oldnoconfig:
++ case syncconfig:
+ /* Update until a loop caused no more changes */
+ do {
+ conf_cnt = 0;
+ check_conf(&rootmenu);
+- } while (conf_cnt &&
+- (input_mode != listnewconfig &&
+- input_mode != oldnoconfig));
++ } while (conf_cnt);
++ break;
++ case olddefconfig:
++ default:
+ break;
+ }
+
+ if (input_mode == savedefconfig) {
+ if (conf_write_defconfig(defconfig_file)) {
+- fprintf(stderr, _("n*** Error while saving defconfig to: %s\n\n"),
++ fprintf(stderr, "n*** Error while saving defconfig to: %s\n\n",
+ defconfig_file);
+ return 1;
+ }
+ } else if (input_mode != listnewconfig) {
+- /*
+- * build so we shall update autoconf.
+- */
+- if (conf_write(NULL)) {
+- fprintf(stderr, _("\n*** Error during writing of the configuration.\n\n"));
++ if (!no_conf_write && conf_write(NULL)) {
++ fprintf(stderr, "\n*** Error during writing of the configuration.\n\n");
+ exit(1);
+ }
+- if (conf_write_autoconf()) {
+- fprintf(stderr, _("\n*** Error during update of the configuration.\n\n"));
++
++ /*
++ * Create auto.conf if it does not exist.
++ * This prevents GNU Make 4.1 or older from emitting
++ * "include/generated/auto.conf: No such file or directory"
++ * in the top-level Makefile
++ *
++ * syncconfig always creates or updates auto.conf because it is
++ * used during the build.
++ */
++
++ /*
++ * In our cmake case, we always want to update the autogenerated
++ * files.
++ */
++ sync_kconfig = 1;
++
++ if (conf_write_autoconf(sync_kconfig) && sync_kconfig) {
++ fprintf(stderr,
++ "\n*** Error during sync of the configuration.\n\n");
+ return 1;
+ }
+ }
+ return 0;
+ }
+-
+-/*
+- * Helper function to facilitate fgets() by Jean Sacren.
+- */
+-void xfgets(char *str, int size, FILE *in)
+-{
+- if (fgets(str, size, in) == NULL)
+- fprintf(stderr, "\nError in reading or end of file.\n");
+-}
+diff --git a/carl9170fw/config/confdata.c b/carl9170fw/config/confdata.c
+index e606f06..d67695d 100644
+--- a/carl9170fw/config/confdata.c
++++ b/carl9170fw/config/confdata.c
+@@ -1,12 +1,13 @@
++// SPDX-License-Identifier: GPL-2.0
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+ #include <sys/stat.h>
+ #include <ctype.h>
+ #include <errno.h>
+ #include <fcntl.h>
++#include <limits.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+@@ -16,6 +17,105 @@
+
+ #include "lkc.h"
+
++/* return true if 'path' exists, false otherwise */
++static bool is_present(const char *path)
++{
++ struct stat st;
++
++ return !stat(path, &st);
++}
++
++/* return true if 'path' exists and it is a directory, false otherwise */
++static bool is_dir(const char *path)
++{
++ struct stat st;
++
++ if (stat(path, &st))
++ return 0;
++
++ return S_ISDIR(st.st_mode);
++}
++
++/*
++ * Create the parent directory of the given path.
++ *
++ * For example, if 'include/config/auto.conf' is given, create 'include/config'.
++ */
++static int make_parent_dir(const char *path)
++{
++ char tmp[PATH_MAX + 1];
++ char *p;
++
++ strncpy(tmp, path, sizeof(tmp));
++ tmp[sizeof(tmp) - 1] = 0;
++
++ /* Remove the base name. Just return if nothing is left */
++ p = strrchr(tmp, '/');
++ if (!p)
++ return 0;
++ *(p + 1) = 0;
++
++ /* Just in case it is an absolute path */
++ p = tmp;
++ while (*p == '/')
++ p++;
++
++ while ((p = strchr(p, '/'))) {
++ *p = 0;
++
++ /* skip if the directory exists */
++ if (!is_dir(tmp) && mkdir(tmp, 0755))
++ return -1;
++
++ *p = '/';
++ while (*p == '/')
++ p++;
++ }
++
++ return 0;
++}
++
++static char depfile_path[PATH_MAX];
++static size_t depfile_prefix_len;
++
++/* touch depfile for symbol 'name' */
++static int conf_touch_dep(const char *name)
++{
++ int fd, ret;
++ const char *s;
++ char *d, c;
++
++ /* check overflow: prefix + name + ".h" + '\0' must fit in buffer. */
++ if (depfile_prefix_len + strlen(name) + 3 > sizeof(depfile_path))
++ return -1;
++
++ d = depfile_path + depfile_prefix_len;
++ s = name;
++
++ while ((c = *s++))
++ *d++ = (c == '_') ? '/' : tolower(c);
++ strcpy(d, ".h");
++
++ /* Assume directory path already exists. */
++ fd = open(depfile_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
++ if (fd == -1) {
++ if (errno != ENOENT)
++ return -1;
++
++ ret = make_parent_dir(depfile_path);
++ if (ret)
++ return ret;
++
++ /* Try it again. */
++ fd = open(depfile_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
++ if (fd == -1)
++ return -1;
++ }
++ close(fd);
++
++ return 0;
++}
++
+ struct conf_printer {
+ void (*print_symbol)(FILE *, struct symbol *, const char *, void *);
+ void (*print_comment)(FILE *, const char *, void *);
+@@ -28,7 +128,7 @@ static void conf_message(const char *fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+
+ static const char *conf_filename;
+-static int conf_lineno, conf_warnings, conf_unsaved;
++static int conf_lineno, conf_warnings;
+
+ const char conf_defname[] = "include/generated/defconfig";
+
+@@ -43,16 +143,16 @@ static void conf_warning(const char *fmt, ...)
+ conf_warnings++;
+ }
+
+-static void conf_default_message_callback(const char *fmt, va_list ap)
++static void conf_default_message_callback(const char *s)
+ {
+ printf("#\n# ");
+- vprintf(fmt, ap);
++ printf("%s", s);
+ printf("\n#\n");
+ }
+
+-static void (*conf_message_callback) (const char *fmt, va_list ap) =
++static void (*conf_message_callback)(const char *s) =
+ conf_default_message_callback;
+-void conf_set_message_callback(void (*fn) (const char *fmt, va_list ap))
++void conf_set_message_callback(void (*fn)(const char *s))
+ {
+ conf_message_callback = fn;
+ }
+@@ -60,10 +160,15 @@ void conf_set_message_callback(void (*fn) (const char *fmt, va_list ap))
+ static void conf_message(const char *fmt, ...)
+ {
+ va_list ap;
++ char buf[4096];
++
++ if (!conf_message_callback)
++ return;
+
+ va_start(ap, fmt);
+- if (conf_message_callback)
+- conf_message_callback(fmt, ap);
++
++ vsnprintf(buf, sizeof(buf), fmt, ap);
++ conf_message_callback(buf);
+ va_end(ap);
+ }
+
+@@ -81,43 +186,16 @@ const char *conf_get_autoconfig_name(void)
+ return name ? name : "include/generated/auto.conf";
+ }
+
+-static char *conf_expand_value(const char *in)
+-{
+- struct symbol *sym;
+- const char *src;
+- static char res_value[SYMBOL_MAXLENGTH];
+- char *dst, name[SYMBOL_MAXLENGTH];
+-
+- res_value[0] = 0;
+- dst = name;
+- while ((src = strchr(in, '$'))) {
+- strncat(res_value, in, src - in);
+- src++;
+- dst = name;
+- while (isalnum(*src) || *src == '_')
+- *dst++ = *src++;
+- *dst = 0;
+- sym = sym_lookup(name, 0);
+- sym_calc_value(sym);
+- strcat(res_value, sym_get_string_value(sym));
+- in = src;
+- }
+- strcat(res_value, in);
+-
+- return res_value;
+-}
+-
+ char *conf_get_default_confname(void)
+ {
+- struct stat buf;
+ static char fullname[PATH_MAX+1];
+ char *env, *name;
+
+- name = conf_expand_value(conf_defname);
++ name = expand_string(conf_defname);
+ env = getenv(SRCTREE);
+ if (env) {
+ sprintf(fullname, "%s/%s", env, name);
+- if (!stat(fullname, &buf))
++ if (is_present(fullname))
+ return fullname;
+ }
+ return name;
+@@ -150,14 +228,6 @@ static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
+ conf_warning("symbol value '%s' invalid for %s",
+ p, sym->name);
+ return 1;
+- case S_OTHER:
+- if (*p != '"') {
+- for (p2 = p; *p2 && !isspace(*p2); p2++)
+- ;
+- sym->type = S_STRING;
+- goto done;
+- }
+- /* fall through */
+ case S_STRING:
+ if (*p++ != '"')
+ break;
+@@ -176,9 +246,8 @@ static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
+ /* fall through */
+ case S_INT:
+ case S_HEX:
+- done:
+ if (sym_string_valid(sym, p)) {
+- sym->def[def].val = strdup(p);
++ sym->def[def].val = xstrdup(p);
+ sym->flags |= def_flags;
+ } else {
+ if (def != S_DEF_AUTO)
+@@ -201,7 +270,7 @@ static int add_byte(int c, char **lineptr, size_t slen, size_t *n)
+ if (new_size > *n) {
+ new_size += LINE_GROWTH - 1;
+ new_size *= 2;
+- nline = realloc(*lineptr, new_size);
++ nline = xrealloc(*lineptr, new_size);
+ if (!nline)
+ return -1;
+
+@@ -274,10 +343,11 @@ int conf_read_simple(const char *name, int def)
+ if (expr_calc_value(prop->visible.expr) == no ||
+ prop->expr->type != E_SYMBOL)
+ continue;
+- name = conf_expand_value(prop->expr->left.sym->name);
++ sym_calc_value(prop->expr->left.sym);
++ name = sym_get_string_value(prop->expr->left.sym);
+ in = zconf_fopen(name);
+ if (in) {
+- conf_message(_("using defaults found in %s"),
++ conf_message("using defaults found in %s",
+ name);
+ goto load;
+ }
+@@ -290,7 +360,6 @@ load:
+ conf_filename = name;
+ conf_lineno = 0;
+ conf_warnings = 0;
+- conf_unsaved = 0;
+
+ def_flags = SYMBOL_DEF << def;
+ for_all_symbols(i, sym) {
+@@ -327,7 +396,7 @@ load:
+ sym = sym_find(line + 2 + strlen(CONFIG_));
+ if (!sym) {
+ sym_add_change_count(1);
+- goto setsym;
++ continue;
+ }
+ } else {
+ sym = sym_lookup(line + 2 + strlen(CONFIG_), 0);
+@@ -357,17 +426,22 @@ load:
+ if (*p2 == '\r')
+ *p2 = 0;
+ }
+- if (def == S_DEF_USER) {
+- sym = sym_find(line + strlen(CONFIG_));
+- if (!sym) {
++
++ sym = sym_find(line + strlen(CONFIG_));
++ if (!sym) {
++ if (def == S_DEF_AUTO)
++ /*
++ * Reading from include/config/auto.conf
++ * If CONFIG_FOO previously existed in
++ * auto.conf but it is missing now,
++ * include/config/foo.h must be touched.
++ */
++ conf_touch_dep(line + strlen(CONFIG_));
++ else
+ sym_add_change_count(1);
+- goto setsym;
+- }
+- } else {
+- sym = sym_lookup(line + strlen(CONFIG_), 0);
+- if (sym->type == S_UNKNOWN)
+- sym->type = S_OTHER;
++ continue;
+ }
++
+ if (sym->flags & def_flags) {
+ conf_warning("override: reassigning to symbol %s", sym->name);
+ }
+@@ -380,7 +454,7 @@ load:
+
+ continue;
+ }
+-setsym:
++
+ if (sym && sym_is_choice_value(sym)) {
+ struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym));
+ switch (sym->def[def].tri) {
+@@ -409,6 +483,7 @@ setsym:
+ int conf_read(const char *name)
+ {
+ struct symbol *sym;
++ int conf_unsaved = 0;
+ int i;
+
+ sym_set_change_count(0);
+@@ -422,7 +497,7 @@ int conf_read(const char *name)
+
+ for_all_symbols(i, sym) {
+ sym_calc_value(sym);
+- if (sym_is_choice(sym) || (sym->flags & SYMBOL_AUTO))
++ if (sym_is_choice(sym) || (sym->flags & SYMBOL_NO_WRITE))
+ continue;
+ if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) {
+ /* check that calculated value agrees with saved value */
+@@ -678,7 +753,6 @@ static void conf_write_symbol(FILE *fp, struct symbol *sym,
+ const char *str;
+
+ switch (sym->type) {
+- case S_OTHER:
+ case S_UNKNOWN:
+ break;
+ case S_STRING:
+@@ -791,15 +865,14 @@ int conf_write(const char *name)
+ struct menu *menu;
+ const char *basename;
+ const char *str;
+- char dirname[PATH_MAX+1], tmpname[PATH_MAX+1], newname[PATH_MAX+1];
++ char dirname[PATH_MAX+1], tmpname[PATH_MAX+22], newname[PATH_MAX+8];
+ char *env;
+
+ dirname[0] = 0;
+ if (name && name[0]) {
+- struct stat st;
+ char *slash;
+
+- if (!stat(name, &st) && S_ISDIR(st.st_mode)) {
++ if (is_dir(name)) {
+ strcpy(dirname, name);
+ strcat(dirname, "/");
+ basename = conf_get_configname();
+@@ -877,33 +950,61 @@ next:
+ return 1;
+ }
+
+- conf_message(_("configuration written to %s"), newname);
++ conf_message("configuration written to %s", newname);
+
+ sym_set_change_count(0);
+
+ return 0;
+ }
+
+-static int conf_split_config(void)
++/* write a dependency file as used by kbuild to track dependencies */
++static int conf_write_dep(const char *name)
++{
++ struct file *file;
++ FILE *out;
++
++ if (!name)
++ name = ".kconfig.d";
++ out = fopen("..config.tmp", "w");
++ if (!out)
++ return 1;
++ fprintf(out, "deps_config := \\\n");
++ for (file = file_list; file; file = file->next) {
++ if (file->next)
++ fprintf(out, "\t%s \\\n", file->name);
++ else
++ fprintf(out, "\t%s\n", file->name);
++ }
++ fprintf(out, "\n%s: \\\n"
++ "\t$(deps_config)\n\n", conf_get_autoconfig_name());
++
++ env_write_dep(out, conf_get_autoconfig_name());
++
++ fprintf(out, "\n$(deps_config): ;\n");
++ fclose(out);
++
++ if (make_parent_dir(name))
++ return 1;
++ rename("..config.tmp", name);
++ return 0;
++}
++
++static int conf_touch_deps(void)
+ {
+ const char *name;
+- char path[PATH_MAX+1];
+- char *s, *d, c;
+ struct symbol *sym;
+- struct stat sb;
+- int res, i, fd;
++ int res, i;
++
++ strcpy(depfile_path, "include/generated/");
++ depfile_prefix_len = strlen(depfile_path);
+
+ name = conf_get_autoconfig_name();
+ conf_read_simple(name, S_DEF_AUTO);
+ sym_calc_value(modules_sym);
+
+- if (chdir("include/generated"))
+- return 1;
+-
+- res = 0;
+ for_all_symbols(i, sym) {
+ sym_calc_value(sym);
+- if ((sym->flags & SYMBOL_AUTO) || !sym->name)
++ if ((sym->flags & SYMBOL_NO_WRITE) || !sym->name)
+ continue;
+ if (sym->flags & SYMBOL_WRITE) {
+ if (sym->flags & SYMBOL_DEF_AUTO) {
+@@ -952,63 +1053,30 @@ static int conf_split_config(void)
+ * different from 'no').
+ */
+
+- /* Replace all '_' and append ".h" */
+- s = sym->name;
+- d = path;
+- while ((c = *s++)) {
+- c = tolower(c);
+- *d++ = (c == '_') ? '/' : c;
+- }
+- strcpy(d, ".h");
+-
+- /* Assume directory path already exists. */
+- fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+- if (fd == -1) {
+- if (errno != ENOENT) {
+- res = 1;
+- break;
+- }
+- /*
+- * Create directory components,
+- * unless they exist already.
+- */
+- d = path;
+- while ((d = strchr(d, '/'))) {
+- *d = 0;
+- if (stat(path, &sb) && mkdir(path, 0755)) {
+- res = 1;
+- goto out;
+- }
+- *d++ = '/';
+- }
+- /* Try it again. */
+- fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+- if (fd == -1) {
+- res = 1;
+- break;
+- }
+- }
+- close(fd);
++ res = conf_touch_dep(sym->name);
++ if (res)
++ return res;
+ }
+-out:
+- if (chdir("../.."))
+- return 1;
+
+- return res;
++ return 0;
+ }
+
+-int conf_write_autoconf(void)
++int conf_write_autoconf(int overwrite)
+ {
+ struct symbol *sym;
+ const char *name;
++ const char *autoconf_name = conf_get_autoconfig_name();
+ FILE *out, *tristate, *out_h, *out_c;
+ int i;
+
++ if (!overwrite && is_present(autoconf_name))
++ return 0;
++
+ sym_clear_all_valid();
+
+- file_write_dep("include/generated/auto.conf.cmd");
++ conf_write_dep("include/generated/auto.conf.cmd");
+
+- if (conf_split_config())
++ if (conf_touch_deps())
+ return 1;
+
+ out = fopen(".tmpconfig", "w");
+@@ -1065,24 +1133,35 @@ int conf_write_autoconf(void)
+ name = getenv("KCONFIG_AUTOHEADER");
+ if (!name)
+ name = "include/generated/autoconf.h";
++ if (make_parent_dir(name))
++ return 1;
+ if (rename(".tmpconfig.h", name))
+ return 1;
++
+ name = getenv("KCONFIG_TRISTATE");
+ if (!name)
+ name = "include/generated/tristate.conf";
++ if (make_parent_dir(name))
++ return 1;
+ if (rename(".tmpconfig_tristate", name))
+ return 1;
++
++ if (make_parent_dir(autoconf_name))
++ return 1;
++
+ name = getenv("KCONFIG_CMAKE");
+ if (!name)
+ name = "config.cmake";
++ if (make_parent_dir(name))
++ return 1;
+ if (rename(".tmpconfig.cmake", name))
+ return 1;
+- name = conf_get_autoconfig_name();
++
+ /*
+ * This must be the last step, kbuild has a dependency on auto.conf
+ * and this marks the successful completion of the previous steps.
+ */
+- if (rename(".tmpconfig", name))
++ if (rename(".tmpconfig", autoconf_name))
+ return 1;
+
+ return 0;
+@@ -1186,7 +1265,7 @@ void set_all_choice_values(struct symbol *csym)
+ bool conf_set_all_new_symbols(enum conf_def_mode mode)
+ {
+ struct symbol *sym, *csym;
+- int i, cnt, pby, pty, ptm; /* pby: probability of boolean = y
++ int i, cnt, pby, pty, ptm; /* pby: probability of bool = y
+ * pty: probability of tristate = y
+ * ptm: probability of tristate = m
+ */
+diff --git a/carl9170fw/config/expr.c b/carl9170fw/config/expr.c
+index 8cee597..77ffff3 100644
+--- a/carl9170fw/config/expr.c
++++ b/carl9170fw/config/expr.c
+@@ -1,8 +1,10 @@
++// SPDX-License-Identifier: GPL-2.0
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
++#include <ctype.h>
++#include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -94,7 +96,7 @@ struct expr *expr_copy(const struct expr *org)
+ e->right.expr = expr_copy(org->right.expr);
+ break;
+ default:
+- printf("can't copy type %d\n", e->type);
++ fprintf(stderr, "can't copy type %d\n", e->type);
+ free(e);
+ e = NULL;
+ break;
+@@ -113,7 +115,7 @@ void expr_free(struct expr *e)
+ break;
+ case E_NOT:
+ expr_free(e->left.expr);
+- return;
++ break;
+ case E_EQUAL:
+ case E_GEQ:
+ case E_GTH:
+@@ -127,7 +129,7 @@ void expr_free(struct expr *e)
+ expr_free(e->right.expr);
+ break;
+ default:
+- printf("how to free type %d?\n", e->type);
++ fprintf(stderr, "how to free type %d?\n", e->type);
+ break;
+ }
+ free(e);
+@@ -138,8 +140,18 @@ static int trans_count;
+ #define e1 (*ep1)
+ #define e2 (*ep2)
+
++/*
++ * expr_eliminate_eq() helper.
++ *
++ * Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does
++ * not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared
++ * against all other leaves. Two equal leaves are both replaced with either 'y'
++ * or 'n' as appropriate for 'type', to be eliminated later.
++ */
+ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2)
+ {
++ /* Recurse down to leaves */
++
+ if (e1->type == type) {
+ __expr_eliminate_eq(type, &e1->left.expr, &e2);
+ __expr_eliminate_eq(type, &e1->right.expr, &e2);
+@@ -150,12 +162,18 @@ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct e
+ __expr_eliminate_eq(type, &e1, &e2->right.expr);
+ return;
+ }
++
++ /* e1 and e2 are leaves. Compare them. */
++
+ if (e1->type == E_SYMBOL && e2->type == E_SYMBOL &&
+ e1->left.sym == e2->left.sym &&
+ (e1->left.sym == &symbol_yes || e1->left.sym == &symbol_no))
+ return;
+ if (!expr_eq(e1, e2))
+ return;
++
++ /* e1 and e2 are equal leaves. Prepare them for elimination. */
++
+ trans_count++;
+ expr_free(e1); expr_free(e2);
+ switch (type) {
+@@ -172,6 +190,35 @@ static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct e
+ }
+ }
+
++/*
++ * Rewrites the expressions 'ep1' and 'ep2' to remove operands common to both.
++ * Example reductions:
++ *
++ * ep1: A && B -> ep1: y
++ * ep2: A && B && C -> ep2: C
++ *
++ * ep1: A || B -> ep1: n
++ * ep2: A || B || C -> ep2: C
++ *
++ * ep1: A && (B && FOO) -> ep1: FOO
++ * ep2: (BAR && B) && A -> ep2: BAR
++ *
++ * ep1: A && (B || C) -> ep1: y
++ * ep2: (C || B) && A -> ep2: y
++ *
++ * Comparisons are done between all operands at the same "level" of && or ||.
++ * For example, in the expression 'e1 && (e2 || e3) && (e4 || e5)', the
++ * following operands will be compared:
++ *
++ * - 'e1', 'e2 || e3', and 'e4 || e5', against each other
++ * - e2 against e3
++ * - e4 against e5
++ *
++ * Parentheses are irrelevant within a single level. 'e1 && (e2 && e3)' and
++ * '(e1 && e2) && e3' are both a single level.
++ *
++ * See __expr_eliminate_eq() as well.
++ */
+ void expr_eliminate_eq(struct expr **ep1, struct expr **ep2)
+ {
+ if (!e1 || !e2)
+@@ -197,6 +244,12 @@ void expr_eliminate_eq(struct expr **ep1, struct expr **ep2)
+ #undef e1
+ #undef e2
+
++/*
++ * Returns true if 'e1' and 'e2' are equal, after minor simplification. Two
++ * &&/|| expressions are considered equal if every operand in one expression
++ * equals some operand in the other (operands do not need to appear in the same
++ * order), recursively.
++ */
+ static int expr_eq(struct expr *e1, struct expr *e2)
+ {
+ int res, old_count;
+@@ -243,6 +296,17 @@ static int expr_eq(struct expr *e1, struct expr *e2)
+ return 0;
+ }
+
++/*
++ * Recursively performs the following simplifications in-place (as well as the
++ * corresponding simplifications with swapped operands):
++ *
++ * expr && n -> n
++ * expr && y -> expr
++ * expr || n -> expr
++ * expr || y -> y
++ *
++ * Returns the optimized expression.
++ */
+ static struct expr *expr_eliminate_yn(struct expr *e)
+ {
+ struct expr *tmp;
+@@ -516,12 +580,21 @@ static struct expr *expr_join_and(struct expr *e1, struct expr *e2)
+ return NULL;
+ }
+
++/*
++ * expr_eliminate_dups() helper.
++ *
++ * Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does
++ * not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared
++ * against all other leaves to look for simplifications.
++ */
+ static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2)
+ {
+ #define e1 (*ep1)
+ #define e2 (*ep2)
+ struct expr *tmp;
+
++ /* Recurse down to leaves */
++
+ if (e1->type == type) {
+ expr_eliminate_dups1(type, &e1->left.expr, &e2);
+ expr_eliminate_dups1(type, &e1->right.expr, &e2);
+@@ -532,6 +605,9 @@ static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct
+ expr_eliminate_dups1(type, &e1, &e2->right.expr);
+ return;
+ }
++
++ /* e1 and e2 are leaves. Compare and process them. */
++
+ if (e1 == e2)
+ return;
+
+@@ -568,6 +644,17 @@ static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct
+ #undef e2
+ }
+
++/*
++ * Rewrites 'e' in-place to remove ("join") duplicate and other redundant
++ * operands.
++ *
++ * Example simplifications:
++ *
++ * A || B || A -> A || B
++ * A && B && A=y -> A=y && B
++ *
++ * Returns the deduplicated expression.
++ */
+ struct expr *expr_eliminate_dups(struct expr *e)
+ {
+ int oldcount;
+@@ -584,6 +671,7 @@ struct expr *expr_eliminate_dups(struct expr *e)
+ ;
+ }
+ if (!trans_count)
++ /* No simplifications done in this pass. We're done */
+ break;
+ e = expr_eliminate_yn(e);
+ }
+@@ -591,6 +679,12 @@ struct expr *expr_eliminate_dups(struct expr *e)
+ return e;
+ }
+
++/*
++ * Performs various simplifications involving logical operators and
++ * comparisons.
++ *
++ * Allocates and returns a new expression.
++ */
+ struct expr *expr_transform(struct expr *e)
+ {
+ struct expr *tmp;
+@@ -805,6 +899,20 @@ bool expr_depends_symbol(struct expr *dep, struct symbol *sym)
+ return false;
+ }
+
++/*
++ * Inserts explicit comparisons of type 'type' to symbol 'sym' into the
++ * expression 'e'.
++ *
++ * Examples transformations for type == E_UNEQUAL, sym == &symbol_no:
++ *
++ * A -> A!=n
++ * !A -> A=n
++ * A && B -> !(A=n || B=n)
++ * A || B -> !(A=n && B=n)
++ * A && (B || C) -> !(A=n || (B=n && C=n))
++ *
++ * Allocates and returns a new expression.
++ */
+ struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym)
+ {
+ struct expr *e1, *e2;
+@@ -874,7 +982,6 @@ enum string_value_kind {
+ k_string,
+ k_signed,
+ k_unsigned,
+- k_invalid
+ };
+
+ union string_value {
+@@ -905,13 +1012,10 @@ static enum string_value_kind expr_parse_string(const char *str,
+ val->u = strtoull(str, &tail, 16);
+ kind = k_unsigned;
+ break;
+- case S_STRING:
+- case S_UNKNOWN:
++ default:
+ val->s = strtoll(str, &tail, 0);
+ kind = k_signed;
+ break;
+- default:
+- return k_invalid;
+ }
+ return !errno && !*tail && tail > str && isxdigit(tail[-1])
+ ? kind : k_string;
+@@ -967,13 +1071,7 @@ tristate expr_calc_value(struct expr *e)
+
+ if (k1 == k_string || k2 == k_string)
+ res = strcmp(str1, str2);
+- else if (k1 == k_invalid || k2 == k_invalid) {
+- if (e->type != E_EQUAL && e->type != E_UNEQUAL) {
+- printf("Cannot compare \"%s\" and \"%s\"\n", str1, str2);
+- return no;
+- }
+- res = strcmp(str1, str2);
+- } else if (k1 == k_unsigned || k2 == k_unsigned)
++ else if (k1 == k_unsigned || k2 == k_unsigned)
+ res = (lval.u > rval.u) - (lval.u < rval.u);
+ else /* if (k1 == k_signed && k2 == k_signed) */
+ res = (lval.s > rval.s) - (lval.s < rval.s);
+@@ -1031,49 +1129,9 @@ static int expr_compare_type(enum expr_type t1, enum expr_type t2)
+ return 0;
+ }
+
+-static inline struct expr *
+-expr_get_leftmost_symbol(const struct expr *e)
+-{
+-
+- if (e == NULL)
+- return NULL;
+-
+- while (e->type != E_SYMBOL)
+- e = e->left.expr;
+-
+- return expr_copy(e);
+-}
+-
+-/*
+- * Given expression `e1' and `e2', returns the leaf of the longest
+- * sub-expression of `e1' not containing 'e2.
+- */
+-struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2)
+-{
+- struct expr *ret;
+-
+- switch (e1->type) {
+- case E_OR:
+- return expr_alloc_and(
+- expr_simplify_unmet_dep(e1->left.expr, e2),
+- expr_simplify_unmet_dep(e1->right.expr, e2));
+- case E_AND: {
+- struct expr *e;
+- e = expr_alloc_and(expr_copy(e1), expr_copy(e2));
+- e = expr_eliminate_dups(e);
+- ret = (!expr_eq(e, e1)) ? e1 : NULL;
+- expr_free(e);
+- break;
+- }
+- default:
+- ret = e1;
+- break;
+- }
+-
+- return expr_get_leftmost_symbol(ret);
+-}
+-
+-void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken)
++void expr_print(struct expr *e,
++ void (*fn)(void *, struct symbol *, const char *),
++ void *data, int prevtoken)
+ {
+ if (!e) {
+ fn(data, NULL, "y");
+@@ -1207,3 +1265,33 @@ void expr_gstr_print(struct expr *e, struct gstr *gs)
+ {
+ expr_print(e, expr_print_gstr_helper, gs, E_NONE);
+ }
++
++/*
++ * Transform the top level "||" tokens into newlines and prepend each
++ * line with a minus. This makes expressions much easier to read.
++ * Suitable for reverse dependency expressions.
++ */
++static void expr_print_revdep(struct expr *e,
++ void (*fn)(void *, struct symbol *, const char *),
++ void *data, tristate pr_type, const char **title)
++{
++ if (e->type == E_OR) {
++ expr_print_revdep(e->left.expr, fn, data, pr_type, title);
++ expr_print_revdep(e->right.expr, fn, data, pr_type, title);
++ } else if (expr_calc_value(e) == pr_type) {
++ if (*title) {
++ fn(data, NULL, *title);
++ *title = NULL;
++ }
++
++ fn(data, NULL, " - ");
++ expr_print(e, fn, data, E_NONE);
++ fn(data, NULL, "\n");
++ }
++}
++
++void expr_gstr_print_revdep(struct expr *e, struct gstr *gs,
++ tristate pr_type, const char *title)
++{
++ expr_print_revdep(e, expr_print_gstr_helper, gs, pr_type, &title);
++}
+diff --git a/carl9170fw/config/expr.h b/carl9170fw/config/expr.h
+index a73f762..999edb6 100644
+--- a/carl9170fw/config/expr.h
++++ b/carl9170fw/config/expr.h
+@@ -1,6 +1,6 @@
++/* SPDX-License-Identifier: GPL-2.0 */
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+ #ifndef EXPR_H
+@@ -62,7 +62,7 @@ struct symbol_value {
+ };
+
+ enum symbol_type {
+- S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING, S_OTHER
++ S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING
+ };
+
+ /* enum values are used as index to symbol.def[] */
+@@ -74,21 +74,64 @@ enum {
+ S_DEF_COUNT
+ };
+
++/*
++ * Represents a configuration symbol.
++ *
++ * Choices are represented as a special kind of symbol and have the
++ * SYMBOL_CHOICE bit set in 'flags'.
++ */
+ struct symbol {
++ /* The next symbol in the same bucket in the symbol hash table */
+ struct symbol *next;
++
++ /* The name of the symbol, e.g. "FOO" for 'config FOO' */
+ char *name;
++
++ /* S_BOOLEAN, S_TRISTATE, ... */
+ enum symbol_type type;
++
++ /*
++ * The calculated value of the symbol. The SYMBOL_VALID bit is set in
++ * 'flags' when this is up to date. Note that this value might differ
++ * from the user value set in e.g. a .config file, due to visibility.
++ */
+ struct symbol_value curr;
++
++ /*
++ * Values for the symbol provided from outside. def[S_DEF_USER] holds
++ * the .config value.
++ */
+ struct symbol_value def[S_DEF_COUNT];
++
++ /*
++ * An upper bound on the tristate value the user can set for the symbol
++ * if it is a boolean or tristate. Calculated from prompt dependencies,
++ * which also inherit dependencies from enclosing menus, choices, and
++ * ifs. If 'n', the user value will be ignored.
++ *
++ * Symbols lacking prompts always have visibility 'n'.
++ */
+ tristate visible;
++
++ /* SYMBOL_* flags */
+ int flags;
++
++ /* List of properties. See prop_type. */
+ struct property *prop;
++
++ /* Dependencies from enclosing menus, choices, and ifs */
+ struct expr_value dir_dep;
++
++ /* Reverse dependencies through being selected by other symbols */
+ struct expr_value rev_dep;
++
++ /*
++ * "Weak" reverse dependencies through being implied by other symbols
++ */
+ struct expr_value implied;
+ };
+
+-#define for_all_symbols(i, sym) for (i = 0; i < SYMBOL_HASHSIZE; i++) for (sym = symbol_hash[i]; sym; sym = sym->next) if (sym->type != S_OTHER)
++#define for_all_symbols(i, sym) for (i = 0; i < SYMBOL_HASHSIZE; i++) for (sym = symbol_hash[i]; sym; sym = sym->next)
+
+ #define SYMBOL_CONST 0x0001 /* symbol is const */
+ #define SYMBOL_CHECK 0x0008 /* used during dependency checking */
+@@ -98,7 +141,7 @@ struct symbol {
+ #define SYMBOL_OPTIONAL 0x0100 /* choice is optional - values can be 'n' */
+ #define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */
+ #define SYMBOL_CHANGED 0x0400 /* ? */
+-#define SYMBOL_AUTO 0x1000 /* value from environment variable */
++#define SYMBOL_NO_WRITE 0x1000 /* Symbol for internal use only; it will not be written */
+ #define SYMBOL_CHECKED 0x2000 /* used during dependency checking */
+ #define SYMBOL_WARNED 0x8000 /* warning has been issued */
+
+@@ -128,18 +171,20 @@ struct symbol {
+ * config BAZ
+ * int "BAZ Value"
+ * range 1..255
++ *
++ * Please, also check zconf.y:print_symbol() when modifying the
++ * list of property types!
+ */
+ enum prop_type {
+ P_UNKNOWN,
+ P_PROMPT, /* prompt "foo prompt" or "BAZ Value" */
+ P_COMMENT, /* text associated with a comment */
+- P_MENU, /* prompt associated with a menuconfig option */
++ P_MENU, /* prompt associated with a menu or menuconfig symbol */
+ P_DEFAULT, /* default y */
+ P_CHOICE, /* choice value */
+ P_SELECT, /* select BAR */
+ P_IMPLY, /* imply BAR */
+ P_RANGE, /* range 7..100 (for a symbol) */
+- P_ENV, /* value from environment variable */
+ P_SYMBOL, /* where a symbol is defined */
+ };
+
+@@ -166,22 +211,67 @@ struct property {
+ for (st = sym->prop; st; st = st->next) \
+ if (st->text)
+
++/*
++ * Represents a node in the menu tree, as seen in e.g. menuconfig (though used
++ * for all front ends). Each symbol, menu, etc. defined in the Kconfig files
++ * gets a node. A symbol defined in multiple locations gets one node at each
++ * location.
++ */
+ struct menu {
++ /* The next menu node at the same level */
+ struct menu *next;
++
++ /* The parent menu node, corresponding to e.g. a menu or choice */
+ struct menu *parent;
++
++ /* The first child menu node, for e.g. menus and choices */
+ struct menu *list;
++
++ /*
++ * The symbol associated with the menu node. Choices are implemented as
++ * a special kind of symbol. NULL for menus, comments, and ifs.
++ */
+ struct symbol *sym;
++
++ /*
++ * The prompt associated with the node. This holds the prompt for a
++ * symbol as well as the text for a menu or comment, along with the
++ * type (P_PROMPT, P_MENU, etc.)
++ */
+ struct property *prompt;
++
++ /*
++ * 'visible if' dependencies. If more than one is given, they will be
++ * ANDed together.
++ */
+ struct expr *visibility;
++
++ /*
++ * Ordinary dependencies from e.g. 'depends on' and 'if', ANDed
++ * together
++ */
+ struct expr *dep;
++
++ /* MENU_* flags */
+ unsigned int flags;
++
++ /* Any help text associated with the node */
+ char *help;
++
++ /* The location where the menu node appears in the Kconfig files */
+ struct file *file;
+ int lineno;
++
++ /* For use by front ends that need to store auxiliary data */
+ void *data;
+ };
+
++/*
++ * Set on a menu node when the corresponding symbol changes state in some way.
++ * Can be checked by front ends.
++ */
+ #define MENU_CHANGED 0x0001
++
+ #define MENU_ROOT 0x0002
+
+ struct jump_key {
+@@ -217,11 +307,12 @@ struct expr *expr_transform(struct expr *e);
+ int expr_contains_symbol(struct expr *dep, struct symbol *sym);
+ bool expr_depends_symbol(struct expr *dep, struct symbol *sym);
+ struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym);
+-struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2);
+
+ void expr_fprint(struct expr *e, FILE *out);
+ struct gstr; /* forward */
+ void expr_gstr_print(struct expr *e, struct gstr *gs);
++void expr_gstr_print_revdep(struct expr *e, struct gstr *gs,
++ tristate pr_type, const char *title);
+
+ static inline int expr_is_yes(struct expr *e)
+ {
+diff --git a/carl9170fw/config/kconf_id.c b/carl9170fw/config/kconf_id.c
+deleted file mode 100644
+index 5abbc72..0000000
+--- a/carl9170fw/config/kconf_id.c
++++ /dev/null
+@@ -1,54 +0,0 @@
+-
+-static struct kconf_id kconf_id_array[] = {
+- { "mainmenu", T_MAINMENU, TF_COMMAND },
+- { "menu", T_MENU, TF_COMMAND },
+- { "endmenu", T_ENDMENU, TF_COMMAND },
+- { "source", T_SOURCE, TF_COMMAND },
+- { "choice", T_CHOICE, TF_COMMAND },
+- { "endchoice", T_ENDCHOICE, TF_COMMAND },
+- { "comment", T_COMMENT, TF_COMMAND },
+- { "config", T_CONFIG, TF_COMMAND },
+- { "menuconfig", T_MENUCONFIG, TF_COMMAND },
+- { "help", T_HELP, TF_COMMAND },
+- { "---help---", T_HELP, TF_COMMAND },
+- { "if", T_IF, TF_COMMAND|TF_PARAM },
+- { "endif", T_ENDIF, TF_COMMAND },
+- { "depends", T_DEPENDS, TF_COMMAND },
+- { "optional", T_OPTIONAL, TF_COMMAND },
+- { "default", T_DEFAULT, TF_COMMAND, S_UNKNOWN },
+- { "prompt", T_PROMPT, TF_COMMAND },
+- { "tristate", T_TYPE, TF_COMMAND, S_TRISTATE },
+- { "def_tristate", T_DEFAULT, TF_COMMAND, S_TRISTATE },
+- { "bool", T_TYPE, TF_COMMAND, S_BOOLEAN },
+- { "boolean", T_TYPE, TF_COMMAND, S_BOOLEAN },
+- { "def_bool", T_DEFAULT, TF_COMMAND, S_BOOLEAN },
+- { "int", T_TYPE, TF_COMMAND, S_INT },
+- { "hex", T_TYPE, TF_COMMAND, S_HEX },
+- { "string", T_TYPE, TF_COMMAND, S_STRING },
+- { "select", T_SELECT, TF_COMMAND },
+- { "imply", T_IMPLY, TF_COMMAND },
+- { "range", T_RANGE, TF_COMMAND },
+- { "visible", T_VISIBLE, TF_COMMAND },
+- { "option", T_OPTION, TF_COMMAND },
+- { "on", T_ON, TF_PARAM },
+- { "modules", T_OPT_MODULES, TF_OPTION },
+- { "defconfig_list", T_OPT_DEFCONFIG_LIST, TF_OPTION },
+- { "env", T_OPT_ENV, TF_OPTION },
+- { "allnoconfig_y", T_OPT_ALLNOCONFIG_Y, TF_OPTION },
+-};
+-
+-#define KCONF_ID_ARRAY_SIZE (sizeof(kconf_id_array)/sizeof(struct kconf_id))
+-
+-static const struct kconf_id *kconf_id_lookup(register const char *str, register unsigned int len)
+-{
+- int i;
+-
+- for (i = 0; i < KCONF_ID_ARRAY_SIZE; i++) {
+- struct kconf_id *id = kconf_id_array+i;
+- int l = strlen(id->name);
+-
+- if (len == l && !memcmp(str, id->name, len))
+- return id;
+- }
+- return NULL;
+-}
+diff --git a/carl9170fw/config/lkc.h b/carl9170fw/config/lkc.h
+index cdcbe43..531ff7c 100644
+--- a/carl9170fw/config/lkc.h
++++ b/carl9170fw/config/lkc.h
+@@ -1,6 +1,6 @@
++/* SPDX-License-Identifier: GPL-2.0 */
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+ #ifndef LKC_H
+@@ -8,15 +8,6 @@
+
+ #include "expr.h"
+
+-#ifndef KBUILD_NO_NLS
+-# include <libintl.h>
+-#else
+-static inline const char *gettext(const char *txt) { return txt; }
+-static inline void textdomain(const char *domainname) {}
+-static inline void bindtextdomain(const char *name, const char *dir) {}
+-static inline char *bind_textdomain_codeset(const char *dn, char *c) { return c; }
+-#endif
+-
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+@@ -29,11 +20,6 @@ extern "C" {
+ #define PACKAGE "linux"
+ #endif
+
+-#define LOCALEDIR "/usr/share/locale"
+-
+-#define _(text) gettext(text)
+-#define N_(text) (text)
+-
+ #ifndef CONFIG_
+ #define CONFIG_ "CONFIG_"
+ #endif
+@@ -44,10 +30,6 @@ static inline const char *CONFIG_prefix(void)
+ #undef CONFIG_
+ #define CONFIG_ CONFIG_prefix()
+
+-#define TF_COMMAND 0x0001
+-#define TF_PARAM 0x0002
+-#define TF_OPTION 0x0004
+-
+ enum conf_def_mode {
+ def_default,
+ def_yes,
+@@ -56,18 +38,7 @@ enum conf_def_mode {
+ def_random
+ };
+
+-#define T_OPT_MODULES 1
+-#define T_OPT_DEFCONFIG_LIST 2
+-#define T_OPT_ENV 3
+-#define T_OPT_ALLNOCONFIG_Y 4
+-
+-struct kconf_id {
+- const char *name;
+- int token;
+- unsigned int flags;
+- enum symbol_type stype;
+-};
+-
++extern int yylineno;
+ void zconfdump(FILE *out);
+ void zconf_starthelp(void);
+ FILE *zconf_fopen(const char *name);
+@@ -100,21 +71,27 @@ void menu_warn(struct menu *menu, const char *fmt, ...);
+ struct menu *menu_add_menu(void);
+ void menu_end_menu(void);
+ void menu_add_entry(struct symbol *sym);
+-void menu_end_entry(void);
+ void menu_add_dep(struct expr *dep);
+ void menu_add_visibility(struct expr *dep);
+ struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep);
+ void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep);
+ void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep);
+-void menu_add_option(int token, char *arg);
++void menu_add_option_modules(void);
++void menu_add_option_defconfig_list(void);
++void menu_add_option_allnoconfig_y(void);
+ void menu_finalize(struct menu *parent);
+ void menu_set_type(int type);
+
+ /* util.c */
+ struct file *file_lookup(const char *name);
+-int file_write_dep(const char *name);
+ void *xmalloc(size_t size);
+ void *xcalloc(size_t nmemb, size_t size);
++void *xrealloc(void *p, size_t size);
++char *xstrdup(const char *s);
++char *xstrndup(const char *s, size_t n);
++
++/* zconf.l */
++int yylex(void);
+
+ struct gstr {
+ size_t len;
+@@ -132,16 +109,13 @@ void str_printf(struct gstr *gs, const char *fmt, ...);
+ const char *str_get(struct gstr *gs);
+
+ /* symbol.c */
+-extern struct expr *sym_env_list;
+-
+-void sym_init(void);
+ void sym_clear_all_valid(void);
+ struct symbol *sym_choice_default(struct symbol *sym);
++struct property *sym_get_range_prop(struct symbol *sym);
+ const char *sym_get_string_default(struct symbol *sym);
+ struct symbol *sym_check_deps(struct symbol *sym);
+ struct property *prop_alloc(enum prop_type type, struct symbol *sym);
+ struct symbol *prop_get_symbol(struct property *prop);
+-struct property *sym_get_env_prop(struct symbol *sym);
+
+ static inline tristate sym_get_tristate_value(struct symbol *sym)
+ {
+diff --git a/carl9170fw/config/lkc_proto.h b/carl9170fw/config/lkc_proto.h
+index 5d86e2d..86c2675 100644
+--- a/carl9170fw/config/lkc_proto.h
++++ b/carl9170fw/config/lkc_proto.h
+@@ -7,10 +7,10 @@ int conf_read(const char *name);
+ int conf_read_simple(const char *name, int);
+ int conf_write_defconfig(const char *name);
+ int conf_write(const char *name);
+-int conf_write_autoconf(void);
++int conf_write_autoconf(int overwrite);
+ bool conf_get_changed(void);
+ void conf_set_changed_callback(void (*fn)(void));
+-void conf_set_message_callback(void (*fn)(const char *fmt, va_list ap));
++void conf_set_message_callback(void (*fn)(const char *s));
+
+ /* menu.c */
+ extern struct menu rootmenu;
+@@ -31,7 +31,6 @@ extern struct symbol * symbol_hash[SYMBOL_HASHSIZE];
+
+ struct symbol * sym_lookup(const char *name, int flags);
+ struct symbol * sym_find(const char *name);
+-const char * sym_expand_string_value(const char *in);
+ const char * sym_escape_string_value(const char *in);
+ struct symbol ** sym_re_search(const char *pattern);
+ const char * sym_type_name(enum symbol_type type);
+@@ -49,5 +48,19 @@ const char * sym_get_string_value(struct symbol *sym);
+
+ const char * prop_get_type_name(enum prop_type type);
+
++/* preprocess.c */
++enum variable_flavor {
++ VAR_SIMPLE,
++ VAR_RECURSIVE,
++ VAR_APPEND,
++};
++void env_write_dep(FILE *f, const char *auto_conf_name);
++void variable_add(const char *name, const char *value,
++ enum variable_flavor flavor);
++void variable_all_del(void);
++char *expand_string(const char *in);
++char *expand_dollar(const char **str);
++char *expand_one_token(const char **str);
++
+ /* expr.c */
+ void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken);
+diff --git a/carl9170fw/config/menu.c b/carl9170fw/config/menu.c
+index e935793..d9d1646 100644
+--- a/carl9170fw/config/menu.c
++++ b/carl9170fw/config/menu.c
+@@ -1,6 +1,6 @@
++// SPDX-License-Identifier: GPL-2.0
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+ #include <ctype.h>
+@@ -62,13 +62,8 @@ void menu_add_entry(struct symbol *sym)
+ menu_add_symbol(P_SYMBOL, sym, NULL);
+ }
+
+-void menu_end_entry(void)
+-{
+-}
+-
+ struct menu *menu_add_menu(void)
+ {
+- menu_end_entry();
+ last_entry_ptr = &current_entry->list;
+ return current_menu = current_entry;
+ }
+@@ -79,19 +74,23 @@ void menu_end_menu(void)
+ current_menu = current_menu->parent;
+ }
+
+-static struct expr *menu_check_dep(struct expr *e)
++/*
++ * Rewrites 'm' to 'm' && MODULES, so that it evaluates to 'n' when running
++ * without modules
++ */
++static struct expr *rewrite_m(struct expr *e)
+ {
+ if (!e)
+ return e;
+
+ switch (e->type) {
+ case E_NOT:
+- e->left.expr = menu_check_dep(e->left.expr);
++ e->left.expr = rewrite_m(e->left.expr);
+ break;
+ case E_OR:
+ case E_AND:
+- e->left.expr = menu_check_dep(e->left.expr);
+- e->right.expr = menu_check_dep(e->right.expr);
++ e->left.expr = rewrite_m(e->left.expr);
++ e->right.expr = rewrite_m(e->right.expr);
+ break;
+ case E_SYMBOL:
+ /* change 'm' into 'm' && MODULES */
+@@ -106,7 +105,7 @@ static struct expr *menu_check_dep(struct expr *e)
+
+ void menu_add_dep(struct expr *dep)
+ {
+- current_entry->dep = expr_alloc_and(current_entry->dep, menu_check_dep(dep));
++ current_entry->dep = expr_alloc_and(current_entry->dep, dep);
+ }
+
+ void menu_set_type(int type)
+@@ -131,7 +130,7 @@ static struct property *menu_add_prop(enum prop_type type, char *prompt, struct
+
+ prop->menu = current_entry;
+ prop->expr = expr;
+- prop->visible.expr = menu_check_dep(dep);
++ prop->visible.expr = dep;
+
+ if (prompt) {
+ if (isspace(*prompt)) {
+@@ -196,31 +195,26 @@ void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep)
+ menu_add_prop(type, NULL, expr_alloc_symbol(sym), dep);
+ }
+
+-void menu_add_option(int token, char *arg)
++void menu_add_option_modules(void)
+ {
+- switch (token) {
+- case T_OPT_MODULES:
+- if (modules_sym)
+- zconf_error("symbol '%s' redefines option 'modules'"
+- " already defined by symbol '%s'",
+- current_entry->sym->name,
+- modules_sym->name
+- );
+- modules_sym = current_entry->sym;
+- break;
+- case T_OPT_DEFCONFIG_LIST:
+- if (!sym_defconfig_list)
+- sym_defconfig_list = current_entry->sym;
+- else if (sym_defconfig_list != current_entry->sym)
+- zconf_error("trying to redefine defconfig symbol");
+- break;
+- case T_OPT_ENV:
+- prop_add_env(arg);
+- break;
+- case T_OPT_ALLNOCONFIG_Y:
+- current_entry->sym->flags |= SYMBOL_ALLNOCONFIG_Y;
+- break;
+- }
++ if (modules_sym)
++ zconf_error("symbol '%s' redefines option 'modules' already defined by symbol '%s'",
++ current_entry->sym->name, modules_sym->name);
++ modules_sym = current_entry->sym;
++}
++
++void menu_add_option_defconfig_list(void)
++{
++ if (!sym_defconfig_list)
++ sym_defconfig_list = current_entry->sym;
++ else if (sym_defconfig_list != current_entry->sym)
++ zconf_error("trying to redefine defconfig symbol");
++ sym_defconfig_list->flags |= SYMBOL_NO_WRITE;
++}
++
++void menu_add_option_allnoconfig_y(void)
++{
++ current_entry->sym->flags |= SYMBOL_ALLNOCONFIG_Y;
+ }
+
+ static int menu_validate_number(struct symbol *sym, struct symbol *sym2)
+@@ -252,6 +246,16 @@ static void sym_check_prop(struct symbol *sym)
+ "'%s': number is invalid",
+ sym->name);
+ }
++ if (sym_is_choice(sym)) {
++ struct property *choice_prop =
++ sym_get_choice_prop(sym2);
++
++ if (!choice_prop ||
++ prop_get_symbol(choice_prop) != sym)
++ prop_warn(prop,
++ "choice default symbol '%s' is not contained in the choice",
++ sym2->name);
++ }
+ break;
+ case P_SELECT:
+ case P_IMPLY:
+@@ -260,13 +264,13 @@ static void sym_check_prop(struct symbol *sym)
+ if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE)
+ prop_warn(prop,
+ "config symbol '%s' uses %s, but is "
+- "not boolean or tristate", sym->name, use);
++ "not bool or tristate", sym->name, use);
+ else if (sym2->type != S_UNKNOWN &&
+ sym2->type != S_BOOLEAN &&
+ sym2->type != S_TRISTATE)
+ prop_warn(prop,
+ "'%s' has wrong type. '%s' only "
+- "accept arguments of boolean and "
++ "accept arguments of bool and "
+ "tristate type", sym2->name, use);
+ break;
+ case P_RANGE:
+@@ -292,6 +296,11 @@ void menu_finalize(struct menu *parent)
+
+ sym = parent->sym;
+ if (parent->list) {
++ /*
++ * This menu node has children. We (recursively) process them
++ * and propagate parent dependencies before moving on.
++ */
++
+ if (sym && sym_is_choice(sym)) {
+ if (sym->type == S_UNKNOWN) {
+ /* find the first choice value to find out choice type */
+@@ -309,30 +318,83 @@ void menu_finalize(struct menu *parent)
+ if (menu->sym && menu->sym->type == S_UNKNOWN)
+ menu_set_type(sym->type);
+ }
++
++ /*
++ * Use the choice itself as the parent dependency of
++ * the contained items. This turns the mode of the
++ * choice into an upper bound on the visibility of the
++ * choice value symbols.
++ */
+ parentdep = expr_alloc_symbol(sym);
+ } else if (parent->prompt)
++ /* Menu node for 'menu' */
+ parentdep = parent->prompt->visible.expr;
+ else
++ /* Menu node for 'if' */
+ parentdep = parent->dep;
+
++ /* For each child menu node... */
+ for (menu = parent->list; menu; menu = menu->next) {
+- basedep = expr_transform(menu->dep);
++ /*
++ * Propagate parent dependencies to the child menu
++ * node, also rewriting and simplifying expressions
++ */
++ basedep = rewrite_m(menu->dep);
++ basedep = expr_transform(basedep);
+ basedep = expr_alloc_and(expr_copy(parentdep), basedep);
+ basedep = expr_eliminate_dups(basedep);
+ menu->dep = basedep;
++
+ if (menu->sym)
++ /*
++ * Note: For symbols, all prompts are included
++ * too in the symbol's own property list
++ */
+ prop = menu->sym->prop;
+ else
++ /*
++ * For non-symbol menu nodes, we just need to
++ * handle the prompt
++ */
+ prop = menu->prompt;
++
++ /* For each property... */
+ for (; prop; prop = prop->next) {
+ if (prop->menu != menu)
++ /*
++ * Two possibilities:
++ *
++ * 1. The property lacks dependencies
++ * and so isn't location-specific,
++ * e.g. an 'option'
++ *
++ * 2. The property belongs to a symbol
++ * defined in multiple locations and
++ * is from some other location. It
++ * will be handled there in that
++ * case.
++ *
++ * Skip the property.
++ */
+ continue;
+- dep = expr_transform(prop->visible.expr);
++
++ /*
++ * Propagate parent dependencies to the
++ * property's condition, rewriting and
++ * simplifying expressions at the same time
++ */
++ dep = rewrite_m(prop->visible.expr);
++ dep = expr_transform(dep);
+ dep = expr_alloc_and(expr_copy(basedep), dep);
+ dep = expr_eliminate_dups(dep);
+ if (menu->sym && menu->sym->type != S_TRISTATE)
+ dep = expr_trans_bool(dep);
+ prop->visible.expr = dep;
++
++ /*
++ * Handle selects and implies, which modify the
++ * dependencies of the selected/implied symbol
++ */
+ if (prop->type == P_SELECT) {
+ struct symbol *es = prop_get_symbol(prop);
+ es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr,
+@@ -344,34 +406,81 @@ void menu_finalize(struct menu *parent)
+ }
+ }
+ }
++
++ if (sym && sym_is_choice(sym))
++ expr_free(parentdep);
++
++ /*
++ * Recursively process children in the same fashion before
++ * moving on
++ */
+ for (menu = parent->list; menu; menu = menu->next)
+ menu_finalize(menu);
+ } else if (sym) {
++ /*
++ * Automatic submenu creation. If sym is a symbol and A, B, C,
++ * ... are consecutive items (symbols, menus, ifs, etc.) that
++ * all depend on sym, then the following menu structure is
++ * created:
++ *
++ * sym
++ * +-A
++ * +-B
++ * +-C
++ * ...
++ *
++ * This also works recursively, giving the following structure
++ * if A is a symbol and B depends on A:
++ *
++ * sym
++ * +-A
++ * | +-B
++ * +-C
++ * ...
++ */
++
+ basedep = parent->prompt ? parent->prompt->visible.expr : NULL;
+ basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no);
+ basedep = expr_eliminate_dups(expr_transform(basedep));
++
++ /* Examine consecutive elements after sym */
+ last_menu = NULL;
+ for (menu = parent->next; menu; menu = menu->next) {
+ dep = menu->prompt ? menu->prompt->visible.expr : menu->dep;
+ if (!expr_contains_symbol(dep, sym))
++ /* No dependency, quit */
+ break;
+ if (expr_depends_symbol(dep, sym))
++ /* Absolute dependency, put in submenu */
+ goto next;
++
++ /*
++ * Also consider it a dependency on sym if our
++ * dependencies contain sym and are a "superset" of
++ * sym's dependencies, e.g. '(sym || Q) && R' when sym
++ * depends on R.
++ *
++ * Note that 'R' might be from an enclosing menu or if,
++ * making this a more common case than it might seem.
++ */
+ dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no);
+ dep = expr_eliminate_dups(expr_transform(dep));
+ dep2 = expr_copy(basedep);
+ expr_eliminate_eq(&dep, &dep2);
+ expr_free(dep);
+ if (!expr_is_yes(dep2)) {
++ /* Not superset, quit */
+ expr_free(dep2);
+ break;
+ }
++ /* Superset, put in submenu */
+ expr_free(dep2);
+ next:
+ menu_finalize(menu);
+ menu->parent = parent;
+ last_menu = menu;
+ }
++ expr_free(basedep);
+ if (last_menu) {
+ parent->list = parent->next;
+ parent->next = last_menu->next;
+@@ -420,6 +529,35 @@ void menu_finalize(struct menu *parent)
+ *ep = expr_alloc_one(E_LIST, NULL);
+ (*ep)->right.sym = menu->sym;
+ }
++
++ /*
++ * This code serves two purposes:
++ *
++ * (1) Flattening 'if' blocks, which do not specify a submenu
++ * and only add dependencies.
++ *
++ * (Automatic submenu creation might still create a submenu
++ * from an 'if' before this code runs.)
++ *
++ * (2) "Undoing" any automatic submenus created earlier below
++ * promptless symbols.
++ *
++ * Before:
++ *
++ * A
++ * if ... (or promptless symbol)
++ * +-B
++ * +-C
++ * D
++ *
++ * After:
++ *
++ * A
++ * if ... (or promptless symbol)
++ * B
++ * C
++ * D
++ */
+ if (menu->list && (!menu->prompt || !menu->prompt->text)) {
+ for (last_menu = menu->list; ; last_menu = last_menu->next) {
+ last_menu->parent = parent;
+@@ -444,6 +582,15 @@ void menu_finalize(struct menu *parent)
+ sym->flags |= SYMBOL_WARNED;
+ }
+
++ /*
++ * For non-optional choices, add a reverse dependency (corresponding to
++ * a select) of '<visibility> && m'. This prevents the user from
++ * setting the choice mode to 'n' when the choice is visible.
++ *
++ * This would also work for non-choice symbols, but only non-optional
++ * choices clear SYMBOL_OPTIONAL as of writing. Choices are implemented
++ * as a type of symbol.
++ */
+ if (sym && !sym_is_optional(sym) && parent->prompt) {
+ sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr,
+ expr_alloc_and(parent->prompt->visible.expr,
+@@ -558,7 +705,7 @@ static void get_prompt_str(struct gstr *r, struct property *prop,
+ struct menu *submenu[8], *menu, *location = NULL;
+ struct jump_key *jump = NULL;
+
+- str_printf(r, _("Prompt: %s\n"), _(prop->text));
++ str_printf(r, "Prompt: %s\n", prop->text);
+ menu = prop->menu->parent;
+ for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent) {
+ bool accessible = menu_is_visible(menu);
+@@ -591,16 +738,16 @@ static void get_prompt_str(struct gstr *r, struct property *prop,
+ }
+
+ if (i > 0) {
+- str_printf(r, _(" Location:\n"));
++ str_printf(r, " Location:\n");
+ for (j = 4; --i >= 0; j += 2) {
+ menu = submenu[i];
+ if (jump && menu == location)
+ jump->offset = strlen(r->s);
+ str_printf(r, "%*c-> %s", j, ' ',
+- _(menu_get_prompt(menu)));
++ menu_get_prompt(menu));
+ if (menu->sym) {
+ str_printf(r, " (%s [=%s])", menu->sym->name ?
+- menu->sym->name : _("<choice>"),
++ menu->sym->name : "<choice>",
+ sym_get_string_value(menu->sym));
+ }
+ str_append(r, "\n");
+@@ -664,27 +811,27 @@ static void get_symbol_str(struct gstr *r, struct symbol *sym,
+
+ prop = get_symbol_prop(sym);
+ if (prop) {
+- str_printf(r, _(" Defined at %s:%d\n"), prop->menu->file->name,
++ str_printf(r, " Defined at %s:%d\n", prop->menu->file->name,
+ prop->menu->lineno);
+ if (!expr_is_yes(prop->visible.expr)) {
+- str_append(r, _(" Depends on: "));
++ str_append(r, " Depends on: ");
+ expr_gstr_print(prop->visible.expr, r);
+ str_append(r, "\n");
+ }
+ }
+
+- get_symbol_props_str(r, sym, P_SELECT, _(" Selects: "));
++ get_symbol_props_str(r, sym, P_SELECT, " Selects: ");
+ if (sym->rev_dep.expr) {
+- str_append(r, _(" Selected by: "));
+- expr_gstr_print(sym->rev_dep.expr, r);
+- str_append(r, "\n");
++ expr_gstr_print_revdep(sym->rev_dep.expr, r, yes, " Selected by [y]:\n");
++ expr_gstr_print_revdep(sym->rev_dep.expr, r, mod, " Selected by [m]:\n");
++ expr_gstr_print_revdep(sym->rev_dep.expr, r, no, " Selected by [n]:\n");
+ }
+
+- get_symbol_props_str(r, sym, P_IMPLY, _(" Implies: "));
++ get_symbol_props_str(r, sym, P_IMPLY, " Implies: ");
+ if (sym->implied.expr) {
+- str_append(r, _(" Implied by: "));
+- expr_gstr_print(sym->implied.expr, r);
+- str_append(r, "\n");
++ expr_gstr_print_revdep(sym->implied.expr, r, yes, " Implied by [y]:\n");
++ expr_gstr_print_revdep(sym->implied.expr, r, mod, " Implied by [m]:\n");
++ expr_gstr_print_revdep(sym->implied.expr, r, no, " Implied by [n]:\n");
+ }
+
+ str_append(r, "\n\n");
+@@ -699,7 +846,7 @@ struct gstr get_relations_str(struct symbol **sym_arr, struct list_head *head)
+ for (i = 0; sym_arr && (sym = sym_arr[i]); i++)
+ get_symbol_str(&res, sym, head);
+ if (!i)
+- str_append(&res, _("No matches found.\n"));
++ str_append(&res, "No matches found.\n");
+ return res;
+ }
+
+@@ -714,7 +861,7 @@ void menu_get_ext_help(struct menu *menu, struct gstr *help)
+ str_printf(help, "%s%s:\n\n", CONFIG_, sym->name);
+ help_text = menu_get_help(menu);
+ }
+- str_printf(help, "%s\n", _(help_text));
++ str_printf(help, "%s\n", help_text);
+ if (sym)
+ get_symbol_str(help, sym, NULL);
+ }
+diff --git a/carl9170fw/config/preprocess.c b/carl9170fw/config/preprocess.c
+new file mode 100644
+index 0000000..592dfbf
+--- /dev/null
++++ b/carl9170fw/config/preprocess.c
+@@ -0,0 +1,573 @@
++// SPDX-License-Identifier: GPL-2.0
++//
++// Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com>
++
++#include <ctype.h>
++#include <stdarg.h>
++#include <stdbool.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include "list.h"
++#include "lkc.h"
++
++#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
++
++static char *expand_string_with_args(const char *in, int argc, char *argv[]);
++
++static void __attribute__((noreturn)) pperror(const char *format, ...)
++{
++ va_list ap;
++
++ fprintf(stderr, "%s:%d: ", current_file->name, yylineno);
++ va_start(ap, format);
++ vfprintf(stderr, format, ap);
++ va_end(ap);
++ fprintf(stderr, "\n");
++
++ exit(1);
++}
++
++/*
++ * Environment variables
++ */
++static LIST_HEAD(env_list);
++
++struct env {
++ char *name;
++ char *value;
++ struct list_head node;
++};
++
++static void env_add(const char *name, const char *value)
++{
++ struct env *e;
++
++ e = xmalloc(sizeof(*e));
++ e->name = xstrdup(name);
++ e->value = xstrdup(value);
++
++ list_add_tail(&e->node, &env_list);
++}
++
++static void env_del(struct env *e)
++{
++ list_del(&e->node);
++ free(e->name);
++ free(e->value);
++ free(e);
++}
++
++/* The returned pointer must be freed when done */
++static char *env_expand(const char *name)
++{
++ struct env *e;
++ const char *value;
++
++ if (!*name)
++ return NULL;
++
++ list_for_each_entry(e, &env_list, node) {
++ if (!strcmp(name, e->name))
++ return xstrdup(e->value);
++ }
++
++ value = getenv(name);
++ if (!value)
++ return NULL;
++
++ /*
++ * We need to remember all referenced environment variables.
++ * They will be written out to include/config/auto.conf.cmd
++ */
++ env_add(name, value);
++
++ return xstrdup(value);
++}
++
++void env_write_dep(FILE *f, const char *autoconfig_name)
++{
++ struct env *e, *tmp;
++
++ list_for_each_entry_safe(e, tmp, &env_list, node) {
++ fprintf(f, "ifneq \"$(%s)\" \"%s\"\n", e->name, e->value);
++ fprintf(f, "%s: FORCE\n", autoconfig_name);
++ fprintf(f, "endif\n");
++ env_del(e);
++ }
++}
++
++/*
++ * Built-in functions
++ */
++struct function {
++ const char *name;
++ unsigned int min_args;
++ unsigned int max_args;
++ char *(*func)(int argc, char *argv[]);
++};
++
++static char *do_error_if(int argc, char *argv[])
++{
++ if (!strcmp(argv[0], "y"))
++ pperror("%s", argv[1]);
++
++ return NULL;
++}
++
++static char *do_filename(int argc, char *argv[])
++{
++ return xstrdup(current_file->name);
++}
++
++static char *do_info(int argc, char *argv[])
++{
++ printf("%s\n", argv[0]);
++
++ return xstrdup("");
++}
++
++static char *do_lineno(int argc, char *argv[])
++{
++ char buf[16];
++
++ sprintf(buf, "%d", yylineno);
++
++ return xstrdup(buf);
++}
++
++static char *do_shell(int argc, char *argv[])
++{
++ FILE *p;
++ char buf[256];
++ char *cmd;
++ size_t nread;
++ int i;
++
++ cmd = argv[0];
++
++ p = popen(cmd, "r");
++ if (!p) {
++ perror(cmd);
++ exit(1);
++ }
++
++ nread = fread(buf, 1, sizeof(buf), p);
++ if (nread == sizeof(buf))
++ nread--;
++
++ /* remove trailing new lines */
++ while (nread > 0 && buf[nread - 1] == '\n')
++ nread--;
++
++ buf[nread] = 0;
++
++ /* replace a new line with a space */
++ for (i = 0; i < nread; i++) {
++ if (buf[i] == '\n')
++ buf[i] = ' ';
++ }
++
++ if (pclose(p) == -1) {
++ perror(cmd);
++ exit(1);
++ }
++
++ return xstrdup(buf);
++}
++
++static char *do_warning_if(int argc, char *argv[])
++{
++ if (!strcmp(argv[0], "y"))
++ fprintf(stderr, "%s:%d: %s\n",
++ current_file->name, yylineno, argv[1]);
++
++ return xstrdup("");
++}
++
++static const struct function function_table[] = {
++ /* Name MIN MAX Function */
++ { "error-if", 2, 2, do_error_if },
++ { "filename", 0, 0, do_filename },
++ { "info", 1, 1, do_info },
++ { "lineno", 0, 0, do_lineno },
++ { "shell", 1, 1, do_shell },
++ { "warning-if", 2, 2, do_warning_if },
++};
++
++#define FUNCTION_MAX_ARGS 16
++
++static char *function_expand(const char *name, int argc, char *argv[])
++{
++ const struct function *f;
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(function_table); i++) {
++ f = &function_table[i];
++ if (strcmp(f->name, name))
++ continue;
++
++ if (argc < f->min_args)
++ pperror("too few function arguments passed to '%s'",
++ name);
++
++ if (argc > f->max_args)
++ pperror("too many function arguments passed to '%s'",
++ name);
++
++ return f->func(argc, argv);
++ }
++
++ return NULL;
++}
++
++/*
++ * Variables (and user-defined functions)
++ */
++static LIST_HEAD(variable_list);
++
++struct variable {
++ char *name;
++ char *value;
++ enum variable_flavor flavor;
++ int exp_count;
++ struct list_head node;
++};
++
++static struct variable *variable_lookup(const char *name)
++{
++ struct variable *v;
++
++ list_for_each_entry(v, &variable_list, node) {
++ if (!strcmp(name, v->name))
++ return v;
++ }
++
++ return NULL;
++}
++
++static char *variable_expand(const char *name, int argc, char *argv[])
++{
++ struct variable *v;
++ char *res;
++
++ v = variable_lookup(name);
++ if (!v)
++ return NULL;
++
++ if (argc == 0 && v->exp_count)
++ pperror("Recursive variable '%s' references itself (eventually)",
++ name);
++
++ if (v->exp_count > 1000)
++ pperror("Too deep recursive expansion");
++
++ v->exp_count++;
++
++ if (v->flavor == VAR_RECURSIVE)
++ res = expand_string_with_args(v->value, argc, argv);
++ else
++ res = xstrdup(v->value);
++
++ v->exp_count--;
++
++ return res;
++}
++
++void variable_add(const char *name, const char *value,
++ enum variable_flavor flavor)
++{
++ struct variable *v;
++ char *new_value;
++ bool append = false;
++
++ v = variable_lookup(name);
++ if (v) {
++ /* For defined variables, += inherits the existing flavor */
++ if (flavor == VAR_APPEND) {
++ flavor = v->flavor;
++ append = true;
++ } else {
++ free(v->value);
++ }
++ } else {
++ /* For undefined variables, += assumes the recursive flavor */
++ if (flavor == VAR_APPEND)
++ flavor = VAR_RECURSIVE;
++
++ v = xmalloc(sizeof(*v));
++ v->name = xstrdup(name);
++ v->exp_count = 0;
++ list_add_tail(&v->node, &variable_list);
++ }
++
++ v->flavor = flavor;
++
++ if (flavor == VAR_SIMPLE)
++ new_value = expand_string(value);
++ else
++ new_value = xstrdup(value);
++
++ if (append) {
++ v->value = xrealloc(v->value,
++ strlen(v->value) + strlen(new_value) + 2);
++ strcat(v->value, " ");
++ strcat(v->value, new_value);
++ free(new_value);
++ } else {
++ v->value = new_value;
++ }
++}
++
++static void variable_del(struct variable *v)
++{
++ list_del(&v->node);
++ free(v->name);
++ free(v->value);
++ free(v);
++}
++
++void variable_all_del(void)
++{
++ struct variable *v, *tmp;
++
++ list_for_each_entry_safe(v, tmp, &variable_list, node)
++ variable_del(v);
++}
++
++/*
++ * Evaluate a clause with arguments. argc/argv are arguments from the upper
++ * function call.
++ *
++ * Returned string must be freed when done
++ */
++static char *eval_clause(const char *str, size_t len, int argc, char *argv[])
++{
++ char *tmp, *name, *res, *endptr, *prev, *p;
++ int new_argc = 0;
++ char *new_argv[FUNCTION_MAX_ARGS];
++ int nest = 0;
++ int i;
++ unsigned long n;
++
++ tmp = xstrndup(str, len);
++
++ /*
++ * If variable name is '1', '2', etc. It is generally an argument
++ * from a user-function call (i.e. local-scope variable). If not
++ * available, then look-up global-scope variables.
++ */
++ n = strtoul(tmp, &endptr, 10);
++ if (!*endptr && n > 0 && n <= argc) {
++ res = xstrdup(argv[n - 1]);
++ goto free_tmp;
++ }
++
++ prev = p = tmp;
++
++ /*
++ * Split into tokens
++ * The function name and arguments are separated by a comma.
++ * For example, if the function call is like this:
++ * $(foo,$(x),$(y))
++ *
++ * The input string for this helper should be:
++ * foo,$(x),$(y)
++ *
++ * and split into:
++ * new_argv[0] = 'foo'
++ * new_argv[1] = '$(x)'
++ * new_argv[2] = '$(y)'
++ */
++ while (*p) {
++ if (nest == 0 && *p == ',') {
++ *p = 0;
++ if (new_argc >= FUNCTION_MAX_ARGS)
++ pperror("too many function arguments");
++ new_argv[new_argc++] = prev;
++ prev = p + 1;
++ } else if (*p == '(') {
++ nest++;
++ } else if (*p == ')') {
++ nest--;
++ }
++
++ p++;
++ }
++ new_argv[new_argc++] = prev;
++
++ /*
++ * Shift arguments
++ * new_argv[0] represents a function name or a variable name. Put it
++ * into 'name', then shift the rest of the arguments. This simplifies
++ * 'const' handling.
++ */
++ name = expand_string_with_args(new_argv[0], argc, argv);
++ new_argc--;
++ for (i = 0; i < new_argc; i++)
++ new_argv[i] = expand_string_with_args(new_argv[i + 1],
++ argc, argv);
++
++ /* Search for variables */
++ res = variable_expand(name, new_argc, new_argv);
++ if (res)
++ goto free;
++
++ /* Look for built-in functions */
++ res = function_expand(name, new_argc, new_argv);
++ if (res)
++ goto free;
++
++ /* Last, try environment variable */
++ if (new_argc == 0) {
++ res = env_expand(name);
++ if (res)
++ goto free;
++ }
++
++ res = xstrdup("");
++free:
++ for (i = 0; i < new_argc; i++)
++ free(new_argv[i]);
++ free(name);
++free_tmp:
++ free(tmp);
++
++ return res;
++}
++
++/*
++ * Expand a string that follows '$'
++ *
++ * For example, if the input string is
++ * ($(FOO)$($(BAR)))$(BAZ)
++ * this helper evaluates
++ * $($(FOO)$($(BAR)))
++ * and returns a new string containing the expansion (note that the string is
++ * recursively expanded), also advancing 'str' to point to the next character
++ * after the corresponding closing parenthesis, in this case, *str will be
++ * $(BAR)
++ */
++static char *expand_dollar_with_args(const char **str, int argc, char *argv[])
++{
++ const char *p = *str;
++ const char *q;
++ int nest = 0;
++
++ /*
++ * In Kconfig, variable/function references always start with "$(".
++ * Neither single-letter variables as in $A nor curly braces as in ${CC}
++ * are supported. '$' not followed by '(' loses its special meaning.
++ */
++ if (*p != '(') {
++ *str = p;
++ return xstrdup("$");
++ }
++
++ p++;
++ q = p;
++ while (*q) {
++ if (*q == '(') {
++ nest++;
++ } else if (*q == ')') {
++ if (nest-- == 0)
++ break;
++ }
++ q++;
++ }
++
++ if (!*q)
++ pperror("unterminated reference to '%s': missing ')'", p);
++
++ /* Advance 'str' to after the expanded initial portion of the string */
++ *str = q + 1;
++
++ return eval_clause(p, q - p, argc, argv);
++}
++
++char *expand_dollar(const char **str)
++{
++ return expand_dollar_with_args(str, 0, NULL);
++}
++
++static char *__expand_string(const char **str, bool (*is_end)(char c),
++ int argc, char *argv[])
++{
++ const char *in, *p;
++ char *expansion, *out;
++ size_t in_len, out_len;
++
++ out = xmalloc(1);
++ *out = 0;
++ out_len = 1;
++
++ p = in = *str;
++
++ while (1) {
++ if (*p == '$') {
++ in_len = p - in;
++ p++;
++ expansion = expand_dollar_with_args(&p, argc, argv);
++ out_len += in_len + strlen(expansion);
++ out = xrealloc(out, out_len);
++ strncat(out, in, in_len);
++ strcat(out, expansion);
++ free(expansion);
++ in = p;
++ continue;
++ }
++
++ if (is_end(*p))
++ break;
++
++ p++;
++ }
++
++ in_len = p - in;
++ out_len += in_len;
++ out = xrealloc(out, out_len);
++ strncat(out, in, in_len);
++
++ /* Advance 'str' to the end character */
++ *str = p;
++
++ return out;
++}
++
++static bool is_end_of_str(char c)
++{
++ return !c;
++}
++
++/*
++ * Expand variables and functions in the given string. Undefined variables
++ * expand to an empty string.
++ * The returned string must be freed when done.
++ */
++static char *expand_string_with_args(const char *in, int argc, char *argv[])
++{
++ return __expand_string(&in, is_end_of_str, argc, argv);
++}
++
++char *expand_string(const char *in)
++{
++ return expand_string_with_args(in, 0, NULL);
++}
++
++static bool is_end_of_token(char c)
++{
++ return !(isalnum(c) || c == '_' || c == '-');
++}
++
++/*
++ * Expand variables in a token. The parsing stops when a token separater
++ * (in most cases, it is a whitespace) is encountered. 'str' is updated to
++ * point to the next character.
++ *
++ * The returned string must be freed when done.
++ */
++char *expand_one_token(const char **str)
++{
++ return __expand_string(str, is_end_of_token, 0, NULL);
++}
+diff --git a/carl9170fw/config/symbol.c b/carl9170fw/config/symbol.c
+index 3c8bd9b..1f9266d 100644
+--- a/carl9170fw/config/symbol.c
++++ b/carl9170fw/config/symbol.c
+@@ -1,6 +1,6 @@
++// SPDX-License-Identifier: GPL-2.0
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+ #include <ctype.h>
+@@ -33,33 +33,6 @@ struct symbol *sym_defconfig_list;
+ struct symbol *modules_sym;
+ tristate modules_val;
+
+-struct expr *sym_env_list;
+-
+-static void sym_add_default(struct symbol *sym, const char *def)
+-{
+- struct property *prop = prop_alloc(P_DEFAULT, sym);
+-
+- prop->expr = expr_alloc_symbol(sym_lookup(def, SYMBOL_CONST));
+-}
+-
+-void sym_init(void)
+-{
+- struct symbol *sym;
+- struct utsname uts;
+- static bool inited = false;
+-
+- if (inited)
+- return;
+- inited = true;
+-
+- uname(&uts);
+-
+- sym = sym_lookup("UNAME_RELEASE", 0);
+- sym->type = S_STRING;
+- sym->flags |= SYMBOL_AUTO;
+- sym_add_default(sym, uts.release);
+-}
+-
+ enum symbol_type sym_get_type(struct symbol *sym)
+ {
+ enum symbol_type type = sym->type;
+@@ -77,7 +50,7 @@ const char *sym_type_name(enum symbol_type type)
+ {
+ switch (type) {
+ case S_BOOLEAN:
+- return "boolean";
++ return "bool";
+ case S_TRISTATE:
+ return "tristate";
+ case S_INT:
+@@ -88,8 +61,6 @@ const char *sym_type_name(enum symbol_type type)
+ return "string";
+ case S_UNKNOWN:
+ return "unknown";
+- case S_OTHER:
+- break;
+ }
+ return "???";
+ }
+@@ -103,15 +74,6 @@ struct property *sym_get_choice_prop(struct symbol *sym)
+ return NULL;
+ }
+
+-struct property *sym_get_env_prop(struct symbol *sym)
+-{
+- struct property *prop;
+-
+- for_all_properties(sym, prop, P_ENV)
+- return prop;
+- return NULL;
+-}
+-
+ static struct property *sym_get_default_prop(struct symbol *sym)
+ {
+ struct property *prop;
+@@ -124,7 +86,7 @@ static struct property *sym_get_default_prop(struct symbol *sym)
+ return NULL;
+ }
+
+-static struct property *sym_get_range_prop(struct symbol *sym)
++struct property *sym_get_range_prop(struct symbol *sym)
+ {
+ struct property *prop;
+
+@@ -183,7 +145,7 @@ static void sym_validate_range(struct symbol *sym)
+ sprintf(str, "%lld", val2);
+ else
+ sprintf(str, "0x%llx", val2);
+- sym->curr.val = strdup(str);
++ sym->curr.val = xstrdup(str);
+ }
+
+ static void sym_set_changed(struct symbol *sym)
+@@ -243,7 +205,7 @@ static void sym_calc_visibility(struct symbol *sym)
+ tri = yes;
+ if (sym->dir_dep.expr)
+ tri = expr_calc_value(sym->dir_dep.expr);
+- if (tri == mod)
++ if (tri == mod && sym_get_type(sym) == S_BOOLEAN)
+ tri = yes;
+ if (sym->dir_dep.tri != tri) {
+ sym->dir_dep.tri = tri;
+@@ -333,6 +295,27 @@ static struct symbol *sym_calc_choice(struct symbol *sym)
+ return def_sym;
+ }
+
++static void sym_warn_unmet_dep(struct symbol *sym)
++{
++ struct gstr gs = str_new();
++
++ str_printf(&gs,
++ "\nWARNING: unmet direct dependencies detected for %s\n",
++ sym->name);
++ str_printf(&gs,
++ " Depends on [%c]: ",
++ sym->dir_dep.tri == mod ? 'm' : 'n');
++ expr_gstr_print(sym->dir_dep.expr, &gs);
++ str_printf(&gs, "\n");
++
++ expr_gstr_print_revdep(sym->rev_dep.expr, &gs, yes,
++ " Selected by [y]:\n");
++ expr_gstr_print_revdep(sym->rev_dep.expr, &gs, mod,
++ " Selected by [m]:\n");
++
++ fputs(str_get(&gs), stderr);
++}
++
+ void sym_calc_value(struct symbol *sym)
+ {
+ struct symbol_value newval, oldval;
+@@ -371,11 +354,13 @@ void sym_calc_value(struct symbol *sym)
+ sym->curr.tri = no;
+ return;
+ }
+- if (!sym_is_choice_value(sym))
+- sym->flags &= ~SYMBOL_WRITE;
++ sym->flags &= ~SYMBOL_WRITE;
+
+ sym_calc_visibility(sym);
+
++ if (sym->visible != no)
++ sym->flags |= SYMBOL_WRITE;
++
+ /* set default if recursively called */
+ sym->curr = newval;
+
+@@ -390,7 +375,6 @@ void sym_calc_value(struct symbol *sym)
+ /* if the symbol is visible use the user value
+ * if available, otherwise try the default value
+ */
+- sym->flags |= SYMBOL_WRITE;
+ if (sym_has_value(sym)) {
+ newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri,
+ sym->visible);
+@@ -402,9 +386,10 @@ void sym_calc_value(struct symbol *sym)
+ if (!sym_is_choice(sym)) {
+ prop = sym_get_default_prop(sym);
+ if (prop) {
+- sym->flags |= SYMBOL_WRITE;
+ newval.tri = EXPR_AND(expr_calc_value(prop->expr),
+ prop->visible.tri);
++ if (newval.tri != no)
++ sym->flags |= SYMBOL_WRITE;
+ }
+ if (sym->implied.tri != no) {
+ sym->flags |= SYMBOL_WRITE;
+@@ -412,18 +397,8 @@ void sym_calc_value(struct symbol *sym)
+ }
+ }
+ calc_newval:
+- if (sym->dir_dep.tri == no && sym->rev_dep.tri != no) {
+- struct expr *e;
+- e = expr_simplify_unmet_dep(sym->rev_dep.expr,
+- sym->dir_dep.expr);
+- fprintf(stderr, "warning: (");
+- expr_fprint(e, stderr);
+- fprintf(stderr, ") selects %s which has unmet direct dependencies (",
+- sym->name);
+- expr_fprint(sym->dir_dep.expr, stderr);
+- fprintf(stderr, ")\n");
+- expr_free(e);
+- }
++ if (sym->dir_dep.tri < sym->rev_dep.tri)
++ sym_warn_unmet_dep(sym);
+ newval.tri = EXPR_OR(newval.tri, sym->rev_dep.tri);
+ }
+ if (newval.tri == mod &&
+@@ -433,12 +408,9 @@ void sym_calc_value(struct symbol *sym)
+ case S_STRING:
+ case S_HEX:
+ case S_INT:
+- if (sym->visible != no) {
+- sym->flags |= SYMBOL_WRITE;
+- if (sym_has_value(sym)) {
+- newval.val = sym->def[S_DEF_USER].val;
+- break;
+- }
++ if (sym->visible != no && sym_has_value(sym)) {
++ newval.val = sym->def[S_DEF_USER].val;
++ break;
+ }
+ prop = sym_get_default_prop(sym);
+ if (prop) {
+@@ -480,7 +452,7 @@ void sym_calc_value(struct symbol *sym)
+ }
+ }
+
+- if (sym->flags & SYMBOL_AUTO)
++ if (sym->flags & SYMBOL_NO_WRITE)
+ sym->flags &= ~SYMBOL_WRITE;
+
+ if (sym->flags & SYMBOL_NEED_SET_CHOICE_VALUES)
+@@ -783,7 +755,6 @@ const char *sym_get_string_default(struct symbol *sym)
+ return str;
+ case S_STRING:
+ return str;
+- case S_OTHER:
+ case S_UNKNOWN:
+ break;
+ }
+@@ -851,7 +822,7 @@ struct symbol *sym_lookup(const char *name, int flags)
+ : !(symbol->flags & (SYMBOL_CONST|SYMBOL_CHOICE))))
+ return symbol;
+ }
+- new_name = strdup(name);
++ new_name = xstrdup(name);
+ } else {
+ new_name = NULL;
+ hash = 0;
+@@ -896,55 +867,6 @@ struct symbol *sym_find(const char *name)
+ return symbol;
+ }
+
+-/*
+- * Expand symbol's names embedded in the string given in argument. Symbols'
+- * name to be expanded shall be prefixed by a '$'. Unknown symbol expands to
+- * the empty string.
+- */
+-const char *sym_expand_string_value(const char *in)
+-{
+- const char *src;
+- char *res;
+- size_t reslen;
+-
+- reslen = strlen(in) + 1;
+- res = xmalloc(reslen);
+- res[0] = '\0';
+-
+- while ((src = strchr(in, '$'))) {
+- char *p, name[SYMBOL_MAXLENGTH];
+- const char *symval = "";
+- struct symbol *sym;
+- size_t newlen;
+-
+- strncat(res, in, src - in);
+- src++;
+-
+- p = name;
+- while (isalnum(*src) || *src == '_')
+- *p++ = *src++;
+- *p = '\0';
+-
+- sym = sym_find(name);
+- if (sym != NULL) {
+- sym_calc_value(sym);
+- symval = sym_get_string_value(sym);
+- }
+-
+- newlen = strlen(res) + strlen(symval) + strlen(src) + 1;
+- if (newlen > reslen) {
+- reslen = newlen;
+- res = realloc(res, reslen);
+- }
+-
+- strcat(res, symval);
+- in = src;
+- }
+- strcat(res, in);
+-
+- return res;
+-}
+-
+ const char *sym_escape_string_value(const char *in)
+ {
+ const char *p;
+@@ -1086,7 +1008,7 @@ static struct dep_stack {
+ struct dep_stack *prev, *next;
+ struct symbol *sym;
+ struct property *prop;
+- struct expr *expr;
++ struct expr **expr;
+ } *check_top;
+
+ static void dep_stack_insert(struct dep_stack *stack, struct symbol *sym)
+@@ -1150,37 +1072,52 @@ static void sym_check_print_recursive(struct symbol *last_sym)
+ if (stack->sym == last_sym)
+ fprintf(stderr, "%s:%d:error: recursive dependency detected!\n",
+ prop->file->name, prop->lineno);
+- fprintf(stderr, "For a resolution refer to Documentation/kbuild/kconfig-language.txt\n");
+- fprintf(stderr, "subsection \"Kconfig recursive dependency limitations\"\n");
+- if (stack->expr) {
+- fprintf(stderr, "%s:%d:\tsymbol %s %s value contains %s\n",
+- prop->file->name, prop->lineno,
++
++ if (sym_is_choice(sym)) {
++ fprintf(stderr, "%s:%d:\tchoice %s contains symbol %s\n",
++ menu->file->name, menu->lineno,
+ sym->name ? sym->name : "<choice>",
+- prop_get_type_name(prop->type),
+ next_sym->name ? next_sym->name : "<choice>");
+- } else if (stack->prop) {
++ } else if (sym_is_choice_value(sym)) {
++ fprintf(stderr, "%s:%d:\tsymbol %s is part of choice %s\n",
++ menu->file->name, menu->lineno,
++ sym->name ? sym->name : "<choice>",
++ next_sym->name ? next_sym->name : "<choice>");
++ } else if (stack->expr == &sym->dir_dep.expr) {
+ fprintf(stderr, "%s:%d:\tsymbol %s depends on %s\n",
+ prop->file->name, prop->lineno,
+ sym->name ? sym->name : "<choice>",
+ next_sym->name ? next_sym->name : "<choice>");
+- } else if (sym_is_choice(sym)) {
+- fprintf(stderr, "%s:%d:\tchoice %s contains symbol %s\n",
+- menu->file->name, menu->lineno,
++ } else if (stack->expr == &sym->rev_dep.expr) {
++ fprintf(stderr, "%s:%d:\tsymbol %s is selected by %s\n",
++ prop->file->name, prop->lineno,
+ sym->name ? sym->name : "<choice>",
+ next_sym->name ? next_sym->name : "<choice>");
+- } else if (sym_is_choice_value(sym)) {
+- fprintf(stderr, "%s:%d:\tsymbol %s is part of choice %s\n",
+- menu->file->name, menu->lineno,
++ } else if (stack->expr == &sym->implied.expr) {
++ fprintf(stderr, "%s:%d:\tsymbol %s is implied by %s\n",
++ prop->file->name, prop->lineno,
++ sym->name ? sym->name : "<choice>",
++ next_sym->name ? next_sym->name : "<choice>");
++ } else if (stack->expr) {
++ fprintf(stderr, "%s:%d:\tsymbol %s %s value contains %s\n",
++ prop->file->name, prop->lineno,
+ sym->name ? sym->name : "<choice>",
++ prop_get_type_name(prop->type),
+ next_sym->name ? next_sym->name : "<choice>");
+ } else {
+- fprintf(stderr, "%s:%d:\tsymbol %s is selected by %s\n",
++ fprintf(stderr, "%s:%d:\tsymbol %s %s is visible depending on %s\n",
+ prop->file->name, prop->lineno,
+ sym->name ? sym->name : "<choice>",
++ prop_get_type_name(prop->type),
+ next_sym->name ? next_sym->name : "<choice>");
+ }
+ }
+
++ fprintf(stderr,
++ "For a resolution refer to Documentation/kbuild/kconfig-language.txt\n"
++ "subsection \"Kconfig recursive dependency limitations\"\n"
++ "\n");
++
+ if (check_top == &cv_stack)
+ dep_stack_remove();
+ }
+@@ -1215,7 +1152,7 @@ static struct symbol *sym_check_expr_deps(struct expr *e)
+ default:
+ break;
+ }
+- printf("Oops! How to check %d?\n", e->type);
++ fprintf(stderr, "Oops! How to check %d?\n", e->type);
+ return NULL;
+ }
+
+@@ -1228,12 +1165,26 @@ static struct symbol *sym_check_sym_deps(struct symbol *sym)
+
+ dep_stack_insert(&stack, sym);
+
++ stack.expr = &sym->dir_dep.expr;
++ sym2 = sym_check_expr_deps(sym->dir_dep.expr);
++ if (sym2)
++ goto out;
++
++ stack.expr = &sym->rev_dep.expr;
+ sym2 = sym_check_expr_deps(sym->rev_dep.expr);
+ if (sym2)
+ goto out;
+
++ stack.expr = &sym->implied.expr;
++ sym2 = sym_check_expr_deps(sym->implied.expr);
++ if (sym2)
++ goto out;
++
++ stack.expr = NULL;
++
+ for (prop = sym->prop; prop; prop = prop->next) {
+- if (prop->type == P_CHOICE || prop->type == P_SELECT)
++ if (prop->type == P_CHOICE || prop->type == P_SELECT ||
++ prop->type == P_IMPLY)
+ continue;
+ stack.prop = prop;
+ sym2 = sym_check_expr_deps(prop->visible.expr);
+@@ -1241,7 +1192,7 @@ static struct symbol *sym_check_sym_deps(struct symbol *sym)
+ break;
+ if (prop->type != P_DEFAULT || sym_is_choice(sym))
+ continue;
+- stack.expr = prop->expr;
++ stack.expr = &prop->expr;
+ sym2 = sym_check_expr_deps(prop->expr);
+ if (sym2)
+ break;
+@@ -1319,9 +1270,6 @@ struct symbol *sym_check_deps(struct symbol *sym)
+ sym->flags &= ~SYMBOL_CHECK;
+ }
+
+- if (sym2 && sym2 == sym)
+- sym2 = NULL;
+-
+ return sym2;
+ }
+
+@@ -1360,8 +1308,6 @@ const char *prop_get_type_name(enum prop_type type)
+ switch (type) {
+ case P_PROMPT:
+ return "prompt";
+- case P_ENV:
+- return "env";
+ case P_COMMENT:
+ return "comment";
+ case P_MENU:
+@@ -1383,32 +1329,3 @@ const char *prop_get_type_name(enum prop_type type)
+ }
+ return "unknown";
+ }
+-
+-static void prop_add_env(const char *env)
+-{
+- struct symbol *sym, *sym2;
+- struct property *prop;
+- char *p;
+-
+- sym = current_entry->sym;
+- sym->flags |= SYMBOL_AUTO;
+- for_all_properties(sym, prop, P_ENV) {
+- sym2 = prop_get_symbol(prop);
+- if (strcmp(sym2->name, env))
+- menu_warn(current_entry, "redefining environment symbol from %s",
+- sym2->name);
+- return;
+- }
+-
+- prop = prop_alloc(P_ENV, sym);
+- prop->expr = expr_alloc_symbol(sym_lookup(env, SYMBOL_CONST));
+-
+- sym_env_list = expr_alloc_one(E_LIST, sym_env_list);
+- sym_env_list->right.sym = sym;
+-
+- p = getenv(env);
+- if (p)
+- sym_add_default(sym, p);
+- else
+- menu_warn(current_entry, "environment variable %s undefined", env);
+-}
+diff --git a/carl9170fw/config/util.c b/carl9170fw/config/util.c
+index 0e76042..2958539 100644
+--- a/carl9170fw/config/util.c
++++ b/carl9170fw/config/util.c
+@@ -1,8 +1,7 @@
++// SPDX-License-Identifier: GPL-2.0
+ /*
+ * Copyright (C) 2002-2005 Roman Zippel <zippel@linux-m68k.org>
+ * Copyright (C) 2002-2005 Sam Ravnborg <sam@ravnborg.org>
+- *
+- * Released under the terms of the GNU GPL v2.0.
+ */
+
+ #include <stdarg.h>
+@@ -14,69 +13,21 @@
+ struct file *file_lookup(const char *name)
+ {
+ struct file *file;
+- const char *file_name = sym_expand_string_value(name);
+
+ for (file = file_list; file; file = file->next) {
+ if (!strcmp(name, file->name)) {
+- free((void *)file_name);
+ return file;
+ }
+ }
+
+ file = xmalloc(sizeof(*file));
+ memset(file, 0, sizeof(*file));
+- file->name = file_name;
++ file->name = xstrdup(name);
+ file->next = file_list;
+ file_list = file;
+ return file;
+ }
+
+-/* write a dependency file as used by kbuild to track dependencies */
+-int file_write_dep(const char *name)
+-{
+- struct symbol *sym, *env_sym;
+- struct expr *e;
+- struct file *file;
+- FILE *out;
+-
+- if (!name)
+- name = ".kconfig.d";
+- out = fopen("..config.tmp", "w");
+- if (!out)
+- return 1;
+- fprintf(out, "deps_config := \\\n");
+- for (file = file_list; file; file = file->next) {
+- if (file->next)
+- fprintf(out, "\t%s \\\n", file->name);
+- else
+- fprintf(out, "\t%s\n", file->name);
+- }
+- fprintf(out, "\n%s: \\\n"
+- "\t$(deps_config)\n\n", conf_get_autoconfig_name());
+-
+- expr_list_for_each_sym(sym_env_list, e, sym) {
+- struct property *prop;
+- const char *value;
+-
+- prop = sym_get_env_prop(sym);
+- env_sym = prop_get_symbol(prop);
+- if (!env_sym)
+- continue;
+- value = getenv(env_sym->name);
+- if (!value)
+- value = "";
+- fprintf(out, "ifneq \"$(%s)\" \"%s\"\n", env_sym->name, value);
+- fprintf(out, "%s: FORCE\n", conf_get_autoconfig_name());
+- fprintf(out, "endif\n");
+- }
+-
+- fprintf(out, "\n$(deps_config): ;\n");
+- fclose(out);
+- rename("..config.tmp", name);
+- return 0;
+-}
+-
+-
+ /* Allocate initial growable string */
+ struct gstr str_new(void)
+ {
+@@ -104,7 +55,7 @@ void str_append(struct gstr *gs, const char *s)
+ if (s) {
+ l = strlen(gs->s) + strlen(s) + 1;
+ if (l > gs->len) {
+- gs->s = realloc(gs->s, l);
++ gs->s = xrealloc(gs->s, l);
+ gs->len = l;
+ }
+ strcat(gs->s, s);
+@@ -145,3 +96,34 @@ void *xcalloc(size_t nmemb, size_t size)
+ fprintf(stderr, "Out of memory.\n");
+ exit(1);
+ }
++
++void *xrealloc(void *p, size_t size)
++{
++ p = realloc(p, size);
++ if (p)
++ return p;
++ fprintf(stderr, "Out of memory.\n");
++ exit(1);
++}
++
++char *xstrdup(const char *s)
++{
++ char *p;
++
++ p = strdup(s);
++ if (p)
++ return p;
++ fprintf(stderr, "Out of memory.\n");
++ exit(1);
++}
++
++char *xstrndup(const char *s, size_t n)
++{
++ char *p;
++
++ p = strndup(s, n);
++ if (p)
++ return p;
++ fprintf(stderr, "Out of memory.\n");
++ exit(1);
++}
+diff --git a/carl9170fw/config/zconf.l b/carl9170fw/config/zconf.l
+index 9720530..c52cce8 100644
+--- a/carl9170fw/config/zconf.l
++++ b/carl9170fw/config/zconf.l
+@@ -1,13 +1,13 @@
+-%option nostdinit noyywrap never-interactive full ecs
+-%option 8bit nodefault perf-report perf-report
+-%option noinput
+-%x COMMAND HELP STRING PARAM
+-%{
++/* SPDX-License-Identifier: GPL-2.0 */
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
++%option nostdinit noyywrap never-interactive full ecs
++%option 8bit nodefault yylineno
++%x ASSIGN_VAL HELP STRING
++%{
+
++#include <assert.h>
+ #include <limits.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+@@ -15,6 +15,9 @@
+ #include <unistd.h>
+
+ #include "lkc.h"
++#include "zconf.tab.h"
++
++#define YY_DECL static int yylex1(void)
+
+ #define START_STRSIZE 16
+
+@@ -23,6 +26,8 @@ static struct {
+ int lineno;
+ } current_pos;
+
++static int prev_prev_token = T_EOL;
++static int prev_token = T_EOL;
+ static char *text;
+ static int text_size, text_asize;
+
+@@ -35,6 +40,8 @@ struct buffer *current_buf;
+
+ static int last_ts, first_ts;
+
++static char *expand_token(const char *in, size_t n);
++static void append_expanded_string(const char *in);
+ static void zconf_endhelp(void);
+ static void zconf_endfile(void);
+
+@@ -52,7 +59,7 @@ static void append_string(const char *str, int size)
+ if (new_size > text_asize) {
+ new_size += START_STRSIZE - 1;
+ new_size &= -START_STRSIZE;
+- text = realloc(text, new_size);
++ text = xrealloc(text, new_size);
+ text_asize = new_size;
+ }
+ memcpy(text + text_size, str, size);
+@@ -71,7 +78,7 @@ static void warn_ignored_character(char chr)
+ {
+ fprintf(stderr,
+ "%s:%d:warning: ignoring unsupported character '%c'\n",
+- zconf_curname(), zconf_lineno(), chr);
++ current_file->name, yylineno, chr);
+ }
+ %}
+
+@@ -81,116 +88,113 @@ n [A-Za-z0-9_-]
+ int str = 0;
+ int ts, i;
+
+-[ \t]*#.*\n |
+-[ \t]*\n {
+- current_file->lineno++;
+- return T_EOL;
+-}
+-[ \t]*#.*
+-
+-
+-[ \t]+ {
+- BEGIN(COMMAND);
+-}
+-
+-. {
+- unput(yytext[0]);
+- BEGIN(COMMAND);
+-}
+-
+-
+-<COMMAND>{
+- {n}+ {
+- const struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
+- BEGIN(PARAM);
+- current_pos.file = current_file;
+- current_pos.lineno = current_file->lineno;
+- if (id && id->flags & TF_COMMAND) {
+- zconflval.id = id;
+- return id->token;
+- }
+- alloc_string(yytext, yyleng);
+- zconflval.string = text;
+- return T_WORD;
+- }
+- . warn_ignored_character(*yytext);
+- \n {
+- BEGIN(INITIAL);
+- current_file->lineno++;
+- return T_EOL;
+- }
+-}
++#.* /* ignore comment */
++[ \t]* /* whitespaces */
++\\\n /* escaped new line */
++\n return T_EOL;
++"allnoconfig_y" return T_ALLNOCONFIG_Y;
++"bool" return T_BOOL;
++"choice" return T_CHOICE;
++"comment" return T_COMMENT;
++"config" return T_CONFIG;
++"def_bool" return T_DEF_BOOL;
++"def_tristate" return T_DEF_TRISTATE;
++"default" return T_DEFAULT;
++"defconfig_list" return T_DEFCONFIG_LIST;
++"depends" return T_DEPENDS;
++"endchoice" return T_ENDCHOICE;
++"endif" return T_ENDIF;
++"endmenu" return T_ENDMENU;
++"help"|"---help---" return T_HELP;
++"hex" return T_HEX;
++"if" return T_IF;
++"imply" return T_IMPLY;
++"int" return T_INT;
++"mainmenu" return T_MAINMENU;
++"menu" return T_MENU;
++"menuconfig" return T_MENUCONFIG;
++"modules" return T_MODULES;
++"on" return T_ON;
++"option" return T_OPTION;
++"optional" return T_OPTIONAL;
++"prompt" return T_PROMPT;
++"range" return T_RANGE;
++"select" return T_SELECT;
++"source" return T_SOURCE;
++"string" return T_STRING;
++"tristate" return T_TRISTATE;
++"visible" return T_VISIBLE;
++"||" return T_OR;
++"&&" return T_AND;
++"=" return T_EQUAL;
++"!=" return T_UNEQUAL;
++"<" return T_LESS;
++"<=" return T_LESS_EQUAL;
++">" return T_GREATER;
++">=" return T_GREATER_EQUAL;
++"!" return T_NOT;
++"(" return T_OPEN_PAREN;
++")" return T_CLOSE_PAREN;
++":=" return T_COLON_EQUAL;
++"+=" return T_PLUS_EQUAL;
++\"|\' {
++ str = yytext[0];
++ new_string();
++ BEGIN(STRING);
++ }
++{n}+ {
++ alloc_string(yytext, yyleng);
++ yylval.string = text;
++ return T_WORD;
++ }
++({n}|$)+ {
++ /* this token includes at least one '$' */
++ yylval.string = expand_token(yytext, yyleng);
++ if (strlen(yylval.string))
++ return T_WORD;
++ free(yylval.string);
++ }
++. warn_ignored_character(*yytext);
+
+-<PARAM>{
+- "&&" return T_AND;
+- "||" return T_OR;
+- "(" return T_OPEN_PAREN;
+- ")" return T_CLOSE_PAREN;
+- "!" return T_NOT;
+- "=" return T_EQUAL;
+- "!=" return T_UNEQUAL;
+- "<=" return T_LESS_EQUAL;
+- ">=" return T_GREATER_EQUAL;
+- "<" return T_LESS;
+- ">" return T_GREATER;
+- \"|\' {
+- str = yytext[0];
+- new_string();
+- BEGIN(STRING);
+- }
+- \n BEGIN(INITIAL); current_file->lineno++; return T_EOL;
+- ({n}|[/.])+ {
+- const struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
+- if (id && id->flags & TF_PARAM) {
+- zconflval.id = id;
+- return id->token;
+- }
++<ASSIGN_VAL>{
++ [^[:blank:]\n]+.* {
+ alloc_string(yytext, yyleng);
+- zconflval.string = text;
+- return T_WORD;
+- }
+- #.* /* comment */
+- \\\n current_file->lineno++;
+- [[:blank:]]+
+- . warn_ignored_character(*yytext);
+- <<EOF>> {
+- BEGIN(INITIAL);
++ yylval.string = text;
++ return T_ASSIGN_VAL;
+ }
++ \n { BEGIN(INITIAL); return T_EOL; }
++ .
+ }
+
+ <STRING>{
+- [^'"\\\n]+/\n {
+- append_string(yytext, yyleng);
+- zconflval.string = text;
+- return T_WORD_QUOTE;
+- }
+- [^'"\\\n]+ {
++ "$".* append_expanded_string(yytext);
++ [^$'"\\\n]+ {
+ append_string(yytext, yyleng);
+ }
+- \\.?/\n {
+- append_string(yytext + 1, yyleng - 1);
+- zconflval.string = text;
+- return T_WORD_QUOTE;
+- }
+ \\.? {
+ append_string(yytext + 1, yyleng - 1);
+ }
+ \'|\" {
+ if (str == yytext[0]) {
+- BEGIN(PARAM);
+- zconflval.string = text;
++ BEGIN(INITIAL);
++ yylval.string = text;
+ return T_WORD_QUOTE;
+ } else
+ append_string(yytext, 1);
+ }
+ \n {
+- printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno());
+- current_file->lineno++;
++ fprintf(stderr,
++ "%s:%d:warning: multi-line strings not supported\n",
++ zconf_curname(), zconf_lineno());
++ unput('\n');
+ BEGIN(INITIAL);
+- return T_EOL;
++ yylval.string = text;
++ return T_WORD_QUOTE;
+ }
+ <<EOF>> {
+ BEGIN(INITIAL);
++ yylval.string = text;
++ return T_WORD_QUOTE;
+ }
+ }
+
+@@ -218,12 +222,10 @@ n [A-Za-z0-9_-]
+ }
+ }
+ [ \t]*\n/[^ \t\n] {
+- current_file->lineno++;
+ zconf_endhelp();
+ return T_HELPTEXT;
+ }
+ [ \t]*\n {
+- current_file->lineno++;
+ append_string("\n", 1);
+ }
+ [^ \t\n].* {
+@@ -243,6 +245,12 @@ n [A-Za-z0-9_-]
+ }
+
+ <<EOF>> {
++ BEGIN(INITIAL);
++
++ if (prev_token != T_EOL && prev_token != T_HELPTEXT)
++ fprintf(stderr, "%s:%d:warning: no new line at end of file\n",
++ current_file->name, yylineno);
++
+ if (current_file) {
+ zconf_endfile();
+ return T_EOL;
+@@ -252,6 +260,93 @@ n [A-Za-z0-9_-]
+ }
+
+ %%
++
++/* second stage lexer */
++int yylex(void)
++{
++ int token;
++
++repeat:
++ token = yylex1();
++
++ if (prev_token == T_EOL || prev_token == T_HELPTEXT) {
++ if (token == T_EOL) {
++ /* Do not pass unneeded T_EOL to the parser. */
++ goto repeat;
++ } else {
++ /*
++ * For the parser, update file/lineno at the first token
++ * of each statement. Generally, \n is a statement
++ * terminator in Kconfig, but it is not always true
++ * because \n could be escaped by a backslash.
++ */
++ current_pos.file = current_file;
++ current_pos.lineno = yylineno;
++ }
++ }
++
++ if (prev_prev_token == T_EOL && prev_token == T_WORD &&
++ (token == T_EQUAL || token == T_COLON_EQUAL || token == T_PLUS_EQUAL))
++ BEGIN(ASSIGN_VAL);
++
++ prev_prev_token = prev_token;
++ prev_token = token;
++
++ return token;
++}
++
++static char *expand_token(const char *in, size_t n)
++{
++ char *out;
++ int c;
++ char c2;
++ const char *rest, *end;
++
++ new_string();
++ append_string(in, n);
++
++ /* get the whole line because we do not know the end of token. */
++ while ((c = input()) != EOF) {
++ if (c == '\n') {
++ unput(c);
++ break;
++ }
++ c2 = c;
++ append_string(&c2, 1);
++ }
++
++ rest = text;
++ out = expand_one_token(&rest);
++
++ /* push back unused characters to the input stream */
++ end = rest + strlen(rest);
++ while (end > rest)
++ unput(*--end);
++
++ free(text);
++
++ return out;
++}
++
++static void append_expanded_string(const char *str)
++{
++ const char *end;
++ char *res;
++
++ str++;
++
++ res = expand_dollar(&str);
++
++ /* push back unused characters to the input stream */
++ end = str + strlen(str);
++ while (end > str)
++ unput(*--end);
++
++ append_string(res, strlen(res));
++
++ free(res);
++}
++
+ void zconf_starthelp(void)
+ {
+ new_string();
+@@ -261,7 +356,7 @@ void zconf_starthelp(void)
+
+ static void zconf_endhelp(void)
+ {
+- zconflval.string = text;
++ yylval.string = text;
+ BEGIN(INITIAL);
+ }
+
+@@ -294,7 +389,7 @@ void zconf_initscan(const char *name)
+ {
+ yyin = zconf_fopen(name);
+ if (!yyin) {
+- printf("can't find file %s\n", name);
++ fprintf(stderr, "can't find file %s\n", name);
+ exit(1);
+ }
+
+@@ -302,7 +397,7 @@ void zconf_initscan(const char *name)
+ memset(current_buf, 0, sizeof(*current_buf));
+
+ current_file = file_lookup(name);
+- current_file->lineno = 1;
++ yylineno = 1;
+ }
+
+ void zconf_nextfile(const char *name)
+@@ -315,35 +410,34 @@ void zconf_nextfile(const char *name)
+ current_buf->state = YY_CURRENT_BUFFER;
+ yyin = zconf_fopen(file->name);
+ if (!yyin) {
+- printf("%s:%d: can't open file \"%s\"\n",
+- zconf_curname(), zconf_lineno(), file->name);
++ fprintf(stderr, "%s:%d: can't open file \"%s\"\n",
++ zconf_curname(), zconf_lineno(), file->name);
+ exit(1);
+ }
+ yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
+ buf->parent = current_buf;
+ current_buf = buf;
+
+- for (iter = current_file->parent; iter; iter = iter->parent ) {
+- if (!strcmp(current_file->name,iter->name) ) {
+- printf("%s:%d: recursive inclusion detected. "
+- "Inclusion path:\n current file : '%s'\n",
+- zconf_curname(), zconf_lineno(),
+- zconf_curname());
+- iter = current_file->parent;
+- while (iter && \
+- strcmp(iter->name,current_file->name)) {
+- printf(" included from: '%s:%d'\n",
+- iter->name, iter->lineno-1);
++ current_file->lineno = yylineno;
++ file->parent = current_file;
++
++ for (iter = current_file; iter; iter = iter->parent) {
++ if (!strcmp(iter->name, file->name)) {
++ fprintf(stderr,
++ "Recursive inclusion detected.\n"
++ "Inclusion path:\n"
++ " current file : %s\n", file->name);
++ iter = file;
++ do {
+ iter = iter->parent;
+- }
+- if (iter)
+- printf(" included from: '%s:%d'\n",
+- iter->name, iter->lineno+1);
++ fprintf(stderr, " included from: %s:%d\n",
++ iter->name, iter->lineno - 1);
++ } while (strcmp(iter->name, file->name));
+ exit(1);
+ }
+ }
+- file->lineno = 1;
+- file->parent = current_file;
++
++ yylineno = 1;
+ current_file = file;
+ }
+
+@@ -352,6 +446,8 @@ static void zconf_endfile(void)
+ struct buffer *parent;
+
+ current_file = current_file->parent;
++ if (current_file)
++ yylineno = current_file->lineno;
+
+ parent = current_buf->parent;
+ if (parent) {
+diff --git a/carl9170fw/config/zconf.y b/carl9170fw/config/zconf.y
+index 79c4f04..60936c7 100644
+--- a/carl9170fw/config/zconf.y
++++ b/carl9170fw/config/zconf.y
+@@ -1,8 +1,8 @@
+-%{
++/* SPDX-License-Identifier: GPL-2.0 */
+ /*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+- * Released under the terms of the GNU GPL v2.0.
+ */
++%{
+
+ #include <ctype.h>
+ #include <stdarg.h>
+@@ -20,63 +20,69 @@
+
+ int cdebug = PRINTD;
+
+-extern int zconflex(void);
++static void yyerror(const char *err);
+ static void zconfprint(const char *err, ...);
+ static void zconf_error(const char *err, ...);
+-static void zconferror(const char *err);
+-static bool zconf_endtoken(const struct kconf_id *id, int starttoken, int endtoken);
++static bool zconf_endtoken(const char *tokenname,
++ const char *expected_tokenname);
+
+ struct symbol *symbol_hash[SYMBOL_HASHSIZE];
+
+ static struct menu *current_menu, *current_entry;
+
+ %}
+-%expect 32
+
+ %union
+ {
+ char *string;
+- struct file *file;
+ struct symbol *symbol;
+ struct expr *expr;
+ struct menu *menu;
+- const struct kconf_id *id;
++ enum symbol_type type;
++ enum variable_flavor flavor;
+ }
+
+-%token <id>T_MAINMENU
+-%token <id>T_MENU
+-%token <id>T_ENDMENU
+-%token <id>T_SOURCE
+-%token <id>T_CHOICE
+-%token <id>T_ENDCHOICE
+-%token <id>T_COMMENT
+-%token <id>T_CONFIG
+-%token <id>T_MENUCONFIG
+-%token <id>T_HELP
+ %token <string> T_HELPTEXT
+-%token <id>T_IF
+-%token <id>T_ENDIF
+-%token <id>T_DEPENDS
+-%token <id>T_OPTIONAL
+-%token <id>T_PROMPT
+-%token <id>T_TYPE
+-%token <id>T_DEFAULT
+-%token <id>T_SELECT
+-%token <id>T_IMPLY
+-%token <id>T_RANGE
+-%token <id>T_VISIBLE
+-%token <id>T_OPTION
+-%token <id>T_ON
+ %token <string> T_WORD
+ %token <string> T_WORD_QUOTE
+-%token T_UNEQUAL
+-%token T_LESS
+-%token T_LESS_EQUAL
+-%token T_GREATER
+-%token T_GREATER_EQUAL
++%token T_ALLNOCONFIG_Y
++%token T_BOOL
++%token T_CHOICE
+ %token T_CLOSE_PAREN
++%token T_COLON_EQUAL
++%token T_COMMENT
++%token T_CONFIG
++%token T_DEFAULT
++%token T_DEFCONFIG_LIST
++%token T_DEF_BOOL
++%token T_DEF_TRISTATE
++%token T_DEPENDS
++%token T_ENDCHOICE
++%token T_ENDIF
++%token T_ENDMENU
++%token T_HELP
++%token T_HEX
++%token T_IF
++%token T_IMPLY
++%token T_INT
++%token T_MAINMENU
++%token T_MENU
++%token T_MENUCONFIG
++%token T_MODULES
++%token T_ON
+ %token T_OPEN_PAREN
++%token T_OPTION
++%token T_OPTIONAL
++%token T_PLUS_EQUAL
++%token T_PROMPT
++%token T_RANGE
++%token T_SELECT
++%token T_SOURCE
++%token T_STRING
++%token T_TRISTATE
++%token T_VISIBLE
+ %token T_EOL
++%token <string> T_ASSIGN_VAL
+
+ %left T_OR
+ %left T_AND
+@@ -85,13 +91,15 @@ static struct menu *current_menu, *current_entry;
+ %nonassoc T_NOT
+
+ %type <string> prompt
++%type <symbol> nonconst_symbol
+ %type <symbol> symbol
++%type <type> type logic_type default
+ %type <expr> expr
+ %type <expr> if_expr
+-%type <id> end
+-%type <id> option_name
++%type <string> end
+ %type <menu> if_entry menu_entry choice_entry
+-%type <string> symbol_option_arg word_opt
++%type <string> word_opt assign_val
++%type <flavor> assign_op
+
+ %destructor {
+ fprintf(stderr, "%s:%d: missing end statement for this entry\n",
+@@ -100,71 +108,53 @@ static struct menu *current_menu, *current_entry;
+ menu_end_menu();
+ } if_entry menu_entry choice_entry
+
+-%{
+-/* Include zconf_id.c here so it can see the token constants. */
+-#include "kconf_id.c"
+-%}
+-
+ %%
+-input: nl start | start;
++input: mainmenu_stmt stmt_list | stmt_list;
+
+-start: mainmenu_stmt stmt_list | stmt_list;
++/* mainmenu entry */
++
++mainmenu_stmt: T_MAINMENU prompt T_EOL
++{
++ menu_add_prompt(P_MENU, $2, NULL);
++};
+
+ stmt_list:
+ /* empty */
+ | stmt_list common_stmt
+ | stmt_list choice_stmt
+ | stmt_list menu_stmt
+- | stmt_list end { zconf_error("unexpected end statement"); }
+ | stmt_list T_WORD error T_EOL { zconf_error("unknown statement \"%s\"", $2); }
+- | stmt_list option_name error T_EOL
+-{
+- zconf_error("unexpected option \"%s\"", $2->name);
+-}
+ | stmt_list error T_EOL { zconf_error("invalid statement"); }
+ ;
+
+-option_name:
+- T_DEPENDS | T_PROMPT | T_TYPE | T_SELECT | T_IMPLY | T_OPTIONAL | T_RANGE | T_DEFAULT | T_VISIBLE
+-;
+-
+ common_stmt:
+- T_EOL
+- | if_stmt
++ if_stmt
+ | comment_stmt
+ | config_stmt
+ | menuconfig_stmt
+ | source_stmt
++ | assignment_stmt
+ ;
+
+-option_error:
+- T_WORD error T_EOL { zconf_error("unknown option \"%s\"", $1); }
+- | error T_EOL { zconf_error("invalid option"); }
+-;
+-
+-
+ /* config/menuconfig entry */
+
+-config_entry_start: T_CONFIG T_WORD T_EOL
++config_entry_start: T_CONFIG nonconst_symbol T_EOL
+ {
+- struct symbol *sym = sym_lookup($2, 0);
+- sym->flags |= SYMBOL_OPTIONAL;
+- menu_add_entry(sym);
+- printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2);
++ $2->flags |= SYMBOL_OPTIONAL;
++ menu_add_entry($2);
++ printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2->name);
+ };
+
+ config_stmt: config_entry_start config_option_list
+ {
+- menu_end_entry();
+ printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno());
+ };
+
+-menuconfig_entry_start: T_MENUCONFIG T_WORD T_EOL
++menuconfig_entry_start: T_MENUCONFIG nonconst_symbol T_EOL
+ {
+- struct symbol *sym = sym_lookup($2, 0);
+- sym->flags |= SYMBOL_OPTIONAL;
+- menu_add_entry(sym);
+- printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2);
++ $2->flags |= SYMBOL_OPTIONAL;
++ menu_add_entry($2);
++ printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2->name);
+ };
+
+ menuconfig_stmt: menuconfig_entry_start config_option_list
+@@ -173,26 +163,22 @@ menuconfig_stmt: menuconfig_entry_start config_option_list
+ current_entry->prompt->type = P_MENU;
+ else
+ zconfprint("warning: menuconfig statement without prompt");
+- menu_end_entry();
+ printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno());
+ };
+
+ config_option_list:
+ /* empty */
+ | config_option_list config_option
+- | config_option_list symbol_option
+ | config_option_list depends
+ | config_option_list help
+- | config_option_list option_error
+- | config_option_list T_EOL
+ ;
+
+-config_option: T_TYPE prompt_stmt_opt T_EOL
++config_option: type prompt_stmt_opt T_EOL
+ {
+- menu_set_type($1->stype);
++ menu_set_type($1);
+ printd(DEBUG_PARSE, "%s:%d:type(%u)\n",
+ zconf_curname(), zconf_lineno(),
+- $1->stype);
++ $1);
+ };
+
+ config_option: T_PROMPT prompt if_expr T_EOL
+@@ -201,25 +187,25 @@ config_option: T_PROMPT prompt if_expr T_EOL
+ printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno());
+ };
+
+-config_option: T_DEFAULT expr if_expr T_EOL
++config_option: default expr if_expr T_EOL
+ {
+ menu_add_expr(P_DEFAULT, $2, $3);
+- if ($1->stype != S_UNKNOWN)
+- menu_set_type($1->stype);
++ if ($1 != S_UNKNOWN)
++ menu_set_type($1);
+ printd(DEBUG_PARSE, "%s:%d:default(%u)\n",
+ zconf_curname(), zconf_lineno(),
+- $1->stype);
++ $1);
+ };
+
+-config_option: T_SELECT T_WORD if_expr T_EOL
++config_option: T_SELECT nonconst_symbol if_expr T_EOL
+ {
+- menu_add_symbol(P_SELECT, sym_lookup($2, 0), $3);
++ menu_add_symbol(P_SELECT, $2, $3);
+ printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno());
+ };
+
+-config_option: T_IMPLY T_WORD if_expr T_EOL
++config_option: T_IMPLY nonconst_symbol if_expr T_EOL
+ {
+- menu_add_symbol(P_IMPLY, sym_lookup($2, 0), $3);
++ menu_add_symbol(P_IMPLY, $2, $3);
+ printd(DEBUG_PARSE, "%s:%d:imply\n", zconf_curname(), zconf_lineno());
+ };
+
+@@ -229,34 +215,30 @@ config_option: T_RANGE symbol symbol if_expr T_EOL
+ printd(DEBUG_PARSE, "%s:%d:range\n", zconf_curname(), zconf_lineno());
+ };
+
+-symbol_option: T_OPTION symbol_option_list T_EOL
+-;
++config_option: T_OPTION T_MODULES T_EOL
++{
++ menu_add_option_modules();
++};
+
+-symbol_option_list:
+- /* empty */
+- | symbol_option_list T_WORD symbol_option_arg
++config_option: T_OPTION T_DEFCONFIG_LIST T_EOL
+ {
+- const struct kconf_id *id = kconf_id_lookup($2, strlen($2));
+- if (id && id->flags & TF_OPTION)
+- menu_add_option(id->token, $3);
+- else
+- zconfprint("warning: ignoring unknown option %s", $2);
+- free($2);
++ menu_add_option_defconfig_list();
+ };
+
+-symbol_option_arg:
+- /* empty */ { $$ = NULL; }
+- | T_EQUAL prompt { $$ = $2; }
+-;
++config_option: T_OPTION T_ALLNOCONFIG_Y T_EOL
++{
++ menu_add_option_allnoconfig_y();
++};
+
+ /* choice entry */
+
+ choice: T_CHOICE word_opt T_EOL
+ {
+ struct symbol *sym = sym_lookup($2, SYMBOL_CHOICE);
+- sym->flags |= SYMBOL_AUTO;
++ sym->flags |= SYMBOL_NO_WRITE;
+ menu_add_entry(sym);
+ menu_add_expr(P_CHOICE, NULL, NULL);
++ free($2);
+ printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno());
+ };
+
+@@ -267,7 +249,7 @@ choice_entry: choice choice_option_list
+
+ choice_end: end
+ {
+- if (zconf_endtoken($1, T_CHOICE, T_ENDCHOICE)) {
++ if (zconf_endtoken($1, "choice")) {
+ menu_end_menu();
+ printd(DEBUG_PARSE, "%s:%d:endchoice\n", zconf_curname(), zconf_lineno());
+ }
+@@ -281,8 +263,6 @@ choice_option_list:
+ | choice_option_list choice_option
+ | choice_option_list depends
+ | choice_option_list help
+- | choice_option_list T_EOL
+- | choice_option_list option_error
+ ;
+
+ choice_option: T_PROMPT prompt if_expr T_EOL
+@@ -291,15 +271,11 @@ choice_option: T_PROMPT prompt if_expr T_EOL
+ printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno());
+ };
+
+-choice_option: T_TYPE prompt_stmt_opt T_EOL
++choice_option: logic_type prompt_stmt_opt T_EOL
+ {
+- if ($1->stype == S_BOOLEAN || $1->stype == S_TRISTATE) {
+- menu_set_type($1->stype);
+- printd(DEBUG_PARSE, "%s:%d:type(%u)\n",
+- zconf_curname(), zconf_lineno(),
+- $1->stype);
+- } else
+- YYERROR;
++ menu_set_type($1);
++ printd(DEBUG_PARSE, "%s:%d:type(%u)\n",
++ zconf_curname(), zconf_lineno(), $1);
+ };
+
+ choice_option: T_OPTIONAL T_EOL
+@@ -308,16 +284,28 @@ choice_option: T_OPTIONAL T_EOL
+ printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno());
+ };
+
+-choice_option: T_DEFAULT T_WORD if_expr T_EOL
++choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL
+ {
+- if ($1->stype == S_UNKNOWN) {
+- menu_add_symbol(P_DEFAULT, sym_lookup($2, 0), $3);
+- printd(DEBUG_PARSE, "%s:%d:default\n",
+- zconf_curname(), zconf_lineno());
+- } else
+- YYERROR;
++ menu_add_symbol(P_DEFAULT, $2, $3);
++ printd(DEBUG_PARSE, "%s:%d:default\n",
++ zconf_curname(), zconf_lineno());
+ };
+
++type:
++ logic_type
++ | T_INT { $$ = S_INT; }
++ | T_HEX { $$ = S_HEX; }
++ | T_STRING { $$ = S_STRING; }
++
++logic_type:
++ T_BOOL { $$ = S_BOOLEAN; }
++ | T_TRISTATE { $$ = S_TRISTATE; }
++
++default:
++ T_DEFAULT { $$ = S_UNKNOWN; }
++ | T_DEF_BOOL { $$ = S_BOOLEAN; }
++ | T_DEF_TRISTATE { $$ = S_TRISTATE; }
++
+ choice_block:
+ /* empty */
+ | choice_block common_stmt
+@@ -325,7 +313,7 @@ choice_block:
+
+ /* if entry */
+
+-if_entry: T_IF expr nl
++if_entry: T_IF expr T_EOL
+ {
+ printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno());
+ menu_add_entry(NULL);
+@@ -335,29 +323,15 @@ if_entry: T_IF expr nl
+
+ if_end: end
+ {
+- if (zconf_endtoken($1, T_IF, T_ENDIF)) {
++ if (zconf_endtoken($1, "if")) {
+ menu_end_menu();
+ printd(DEBUG_PARSE, "%s:%d:endif\n", zconf_curname(), zconf_lineno());
+ }
+ };
+
+-if_stmt: if_entry if_block if_end
++if_stmt: if_entry stmt_list if_end
+ ;
+
+-if_block:
+- /* empty */
+- | if_block common_stmt
+- | if_block menu_stmt
+- | if_block choice_stmt
+-;
+-
+-/* mainmenu entry */
+-
+-mainmenu_stmt: T_MAINMENU prompt nl
+-{
+- menu_add_prompt(P_MENU, $2, NULL);
+-};
+-
+ /* menu entry */
+
+ menu: T_MENU prompt T_EOL
+@@ -367,33 +341,33 @@ menu: T_MENU prompt T_EOL
+ printd(DEBUG_PARSE, "%s:%d:menu\n", zconf_curname(), zconf_lineno());
+ };
+
+-menu_entry: menu visibility_list depends_list
++menu_entry: menu menu_option_list
+ {
+ $$ = menu_add_menu();
+ };
+
+ menu_end: end
+ {
+- if (zconf_endtoken($1, T_MENU, T_ENDMENU)) {
++ if (zconf_endtoken($1, "menu")) {
+ menu_end_menu();
+ printd(DEBUG_PARSE, "%s:%d:endmenu\n", zconf_curname(), zconf_lineno());
+ }
+ };
+
+-menu_stmt: menu_entry menu_block menu_end
++menu_stmt: menu_entry stmt_list menu_end
+ ;
+
+-menu_block:
++menu_option_list:
+ /* empty */
+- | menu_block common_stmt
+- | menu_block menu_stmt
+- | menu_block choice_stmt
++ | menu_option_list visible
++ | menu_option_list depends
+ ;
+
+ source_stmt: T_SOURCE prompt T_EOL
+ {
+ printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), $2);
+ zconf_nextfile($2);
++ free($2);
+ };
+
+ /* comment entry */
+@@ -405,10 +379,13 @@ comment: T_COMMENT prompt T_EOL
+ printd(DEBUG_PARSE, "%s:%d:comment\n", zconf_curname(), zconf_lineno());
+ };
+
+-comment_stmt: comment depends_list
+-{
+- menu_end_entry();
+-};
++comment_stmt: comment comment_option_list
++;
++
++comment_option_list:
++ /* empty */
++ | comment_option_list depends
++;
+
+ /* help option */
+
+@@ -420,18 +397,22 @@ help_start: T_HELP T_EOL
+
+ help: help_start T_HELPTEXT
+ {
++ if (current_entry->help) {
++ free(current_entry->help);
++ zconfprint("warning: '%s' defined with more than one help text -- only the last one will be used",
++ current_entry->sym->name ?: "<choice>");
++ }
++
++ /* Is the help text empty or all whitespace? */
++ if ($2[strspn($2, " \f\n\r\t\v")] == '\0')
++ zconfprint("warning: '%s' defined with blank help text",
++ current_entry->sym->name ?: "<choice>");
++
+ current_entry->help = $2;
+ };
+
+ /* depends option */
+
+-depends_list:
+- /* empty */
+- | depends_list depends
+- | depends_list T_EOL
+- | depends_list option_error
+-;
+-
+ depends: T_DEPENDS T_ON expr T_EOL
+ {
+ menu_add_dep($3);
+@@ -439,14 +420,7 @@ depends: T_DEPENDS T_ON expr T_EOL
+ };
+
+ /* visibility option */
+-
+-visibility_list:
+- /* empty */
+- | visibility_list visible
+- | visibility_list T_EOL
+-;
+-
+-visible: T_VISIBLE if_expr
++visible: T_VISIBLE if_expr T_EOL
+ {
+ menu_add_visibility($2);
+ };
+@@ -464,14 +438,9 @@ prompt: T_WORD
+ | T_WORD_QUOTE
+ ;
+
+-end: T_ENDMENU T_EOL { $$ = $1; }
+- | T_ENDCHOICE T_EOL { $$ = $1; }
+- | T_ENDIF T_EOL { $$ = $1; }
+-;
+-
+-nl:
+- T_EOL
+- | nl T_EOL
++end: T_ENDMENU T_EOL { $$ = "menu"; }
++ | T_ENDCHOICE T_EOL { $$ = "choice"; }
++ | T_ENDIF T_EOL { $$ = "if"; }
+ ;
+
+ if_expr: /* empty */ { $$ = NULL; }
+@@ -491,13 +460,31 @@ expr: symbol { $$ = expr_alloc_symbol($1); }
+ | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); }
+ ;
+
+-symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); }
++/* For symbol definitions, selects, etc., where quotes are not accepted */
++nonconst_symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); };
++
++symbol: nonconst_symbol
+ | T_WORD_QUOTE { $$ = sym_lookup($1, SYMBOL_CONST); free($1); }
+ ;
+
+ word_opt: /* empty */ { $$ = NULL; }
+ | T_WORD
+
++/* assignment statement */
++
++assignment_stmt: T_WORD assign_op assign_val T_EOL { variable_add($1, $3, $2); free($1); free($3); }
++
++assign_op:
++ T_EQUAL { $$ = VAR_RECURSIVE; }
++ | T_COLON_EQUAL { $$ = VAR_SIMPLE; }
++ | T_PLUS_EQUAL { $$ = VAR_APPEND; }
++;
++
++assign_val:
++ /* empty */ { $$ = xstrdup(""); };
++ | T_ASSIGN_VAL
++;
++
+ %%
+
+ void conf_parse(const char *name)
+@@ -507,61 +494,51 @@ void conf_parse(const char *name)
+
+ zconf_initscan(name);
+
+- sym_init();
+ _menu_init();
+- rootmenu.prompt = menu_add_prompt(P_MENU, "CARL9170 Firmware Configuration", NULL);
+
+ if (getenv("ZCONF_DEBUG"))
+- zconfdebug = 1;
+- zconfparse();
+- if (zconfnerrs)
++ yydebug = 1;
++ yyparse();
++
++ /* Variables are expanded in the parse phase. We can free them here. */
++ variable_all_del();
++
++ if (yynerrs)
+ exit(1);
+ if (!modules_sym)
+ modules_sym = sym_find( "n" );
+
+- rootmenu.prompt->text = _(rootmenu.prompt->text);
+- rootmenu.prompt->text = sym_expand_string_value(rootmenu.prompt->text);
++ if (!menu_has_prompt(&rootmenu)) {
++ current_entry = &rootmenu;
++ menu_add_prompt(P_MENU, "Main menu", NULL);
++ }
+
+ menu_finalize(&rootmenu);
+ for_all_symbols(i, sym) {
+ if (sym_check_deps(sym))
+- zconfnerrs++;
++ yynerrs++;
+ }
+- if (zconfnerrs)
++ if (yynerrs)
+ exit(1);
+ sym_set_change_count(1);
+ }
+
+-static const char *zconf_tokenname(int token)
+-{
+- switch (token) {
+- case T_MENU: return "menu";
+- case T_ENDMENU: return "endmenu";
+- case T_CHOICE: return "choice";
+- case T_ENDCHOICE: return "endchoice";
+- case T_IF: return "if";
+- case T_ENDIF: return "endif";
+- case T_DEPENDS: return "depends";
+- case T_VISIBLE: return "visible";
+- }
+- return "<token>";
+-}
+-
+-static bool zconf_endtoken(const struct kconf_id *id, int starttoken, int endtoken)
++static bool zconf_endtoken(const char *tokenname,
++ const char *expected_tokenname)
+ {
+- if (id->token != endtoken) {
++ if (strcmp(tokenname, expected_tokenname)) {
+ zconf_error("unexpected '%s' within %s block",
+- id->name, zconf_tokenname(starttoken));
+- zconfnerrs++;
++ tokenname, expected_tokenname);
++ yynerrs++;
+ return false;
+ }
+ if (current_menu->file != current_file) {
+ zconf_error("'%s' in different file than '%s'",
+- id->name, zconf_tokenname(starttoken));
++ tokenname, expected_tokenname);
+ fprintf(stderr, "%s:%d: location of the '%s'\n",
+ current_menu->file->name, current_menu->lineno,
+- zconf_tokenname(starttoken));
+- zconfnerrs++;
++ expected_tokenname);
++ yynerrs++;
+ return false;
+ }
+ return true;
+@@ -582,7 +559,7 @@ static void zconf_error(const char *err, ...)
+ {
+ va_list ap;
+
+- zconfnerrs++;
++ yynerrs++;
+ fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno());
+ va_start(ap, err);
+ vfprintf(stderr, err, ap);
+@@ -590,7 +567,7 @@ static void zconf_error(const char *err, ...)
+ fprintf(stderr, "\n");
+ }
+
+-static void zconferror(const char *err)
++static void yyerror(const char *err)
+ {
+ fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err);
+ }
+@@ -623,7 +600,7 @@ static void print_symbol(FILE *out, struct menu *menu)
+ fprintf(out, "\nconfig %s\n", sym->name);
+ switch (sym->type) {
+ case S_BOOLEAN:
+- fputs(" boolean\n", out);
++ fputs(" bool\n", out);
+ break;
+ case S_TRISTATE:
+ fputs(" tristate\n", out);
+@@ -686,6 +663,10 @@ static void print_symbol(FILE *out, struct menu *menu)
+ print_quoted_string(out, prop->text);
+ fputc('\n', out);
+ break;
++ case P_SYMBOL:
++ fputs( " symbol ", out);
++ fprintf(out, "%s\n", prop->sym->name);
++ break;
+ default:
+ fprintf(out, " unknown prop %d!\n", prop->type);
+ break;
+@@ -746,9 +727,5 @@ void zconfdump(FILE *out)
+ }
+ }
+
+-#include "zconf.lex.c"
+ #include "util.c"
+-#include "confdata.c"
+-#include "expr.c"
+-#include "symbol.c"
+ #include "menu.c"
+diff --git a/carl9170fw/include/linux/ieee80211.h b/carl9170fw/include/linux/ieee80211.h
+index 31c59ea..46ce6cf 100644
+--- a/carl9170fw/include/linux/ieee80211.h
++++ b/carl9170fw/include/linux/ieee80211.h
+@@ -897,33 +897,33 @@ struct ieee80211_mgmt {
+ __le16 status_code;
+ /* possibly followed by Challenge text */
+ u8 variable[0];
+- } __packed auth;
++ } __packed __aligned(4) auth;
+ struct {
+ __le16 reason_code;
+- } __packed deauth;
++ } __packed __aligned(4) deauth;
+ struct {
+ __le16 capab_info;
+ __le16 listen_interval;
+ /* followed by SSID and Supported rates */
+ u8 variable[0];
+- } __packed assoc_req;
++ } __packed __aligned(4) assoc_req;
+ struct {
+ __le16 capab_info;
+ __le16 status_code;
+ __le16 aid;
+ /* followed by Supported rates */
+ u8 variable[0];
+- } __packed assoc_resp, reassoc_resp;
++ } __packed __aligned(4) assoc_resp, reassoc_resp;
+ struct {
+ __le16 capab_info;
+ __le16 listen_interval;
+ u8 current_ap[6];
+ /* followed by SSID and Supported rates */
+ u8 variable[0];
+- } __packed reassoc_req;
++ } __packed __aligned(4) reassoc_req;
+ struct {
+ __le16 reason_code;
+- } __packed disassoc;
++ } __packed __aligned(4) disassoc;
+ struct {
+ __le64 timestamp;
+ __le16 beacon_int;
+@@ -931,11 +931,11 @@ struct ieee80211_mgmt {
+ /* followed by some of SSID, Supported rates,
+ * FH Params, DS Params, CF Params, IBSS Params, TIM */
+ u8 variable[0];
+- } __packed beacon;
++ } __packed __aligned(4) beacon;
+ struct {
+ /* only variable items: SSID, Supported rates */
+ u8 variable[0];
+- } __packed probe_req;
++ } __packed __aligned(4) probe_req;
+ struct {
+ __le64 timestamp;
+ __le16 beacon_int;
+@@ -943,7 +943,7 @@ struct ieee80211_mgmt {
+ /* followed by some of SSID, Supported rates,
+ * FH Params, DS Params, CF Params, IBSS Params */
+ u8 variable[0];
+- } __packed probe_resp;
++ } __packed __aligned(4) probe_resp;
+ struct {
+ u8 category;
+ union {
+@@ -1041,8 +1041,8 @@ struct ieee80211_mgmt {
+ u8 variable[0];
+ } __packed ftm;
+ } u;
+- } __packed action;
+- } u;
++ } __packed __aligned(4) action;
++ } u __aligned(2);
+ } __packed __aligned(2);
+
+ /* Supported rates membership selectors */
+@@ -1245,7 +1245,7 @@ struct ieee80211_bar {
+ __u8 ta[6];
+ __le16 control;
+ __le16 start_seq_num;
+-} __packed __aligned(4);
++} __packed __aligned(2);
+
+ /* 802.11 BA(R) control masks */
+ #define IEEE80211_BAR_CTRL_ACK_POLICY_NORMAL 0x0000
+diff --git a/carl9170fw/include/shared/wlan.h b/carl9170fw/include/shared/wlan.h
+index 9c6b7ff..117f005 100644
+--- a/carl9170fw/include/shared/wlan.h
++++ b/carl9170fw/include/shared/wlan.h
+@@ -273,7 +273,7 @@ struct ar9170_tx_frame {
+ struct ieee80211_hdr i3e;
+ u8 payload[0];
+ } data;
+-} __packed;
++} __packed __aligned(4);
+
+ struct carl9170_tx_superframe {
+ struct carl9170_tx_superdesc s;
+diff --git a/carl9170fw/toolchain/Makefile b/carl9170fw/toolchain/Makefile
+index 11db906..43e546d 100644
+--- a/carl9170fw/toolchain/Makefile
++++ b/carl9170fw/toolchain/Makefile
+@@ -1,16 +1,16 @@
+-BINUTILS_VER=2.31.1
++BINUTILS_VER=2.32
+ BINUTILS_TAR=binutils-$(BINUTILS_VER).tar.xz
+ BINUTILS_URL="https://ftp.gnu.org/gnu/binutils/$(BINUTILS_TAR)"
+
+-NEWLIB_VER=3.0.0
++NEWLIB_VER=3.1.0
+ NEWLIB_TAR=newlib-$(NEWLIB_VER).tar.gz
+ NEWLIB_URL="ftp://sourceware.org/pub/newlib/$(NEWLIB_TAR)"
+
+-GCC_VER=8.2.0
++GCC_VER=9.1.0
+ GCC_TAR=gcc-$(GCC_VER).tar.xz
+ GCC_URL="https://ftp.gnu.org/gnu/gcc/gcc-$(GCC_VER)/$(GCC_TAR)"
+
+-MPFR_VER=4.0.1
++MPFR_VER=4.0.2
+ MPFR_TAR=mpfr-$(MPFR_VER).tar.xz
+ MPFR_URL="https://ftp.gnu.org/gnu/mpfr/$(MPFR_TAR)"
+
+diff --git a/carl9170fw/toolchain/SHA256SUMS b/carl9170fw/toolchain/SHA256SUMS
+index 1b65040..3a53959 100644
+--- a/carl9170fw/toolchain/SHA256SUMS
++++ b/carl9170fw/toolchain/SHA256SUMS
+@@ -1,16 +1,6 @@
+-1cf7adf8ff4b5aa49041c8734bbcf1ad18cc4c94d0029aae0f4e48841088479a src/gcc-7.2.0.tar.xz
+-5b76a9b97c9464209772ed25ce55181a7bb144a66e5669aaec945aa64da3189b src/newlib-2.5.0.tar.gz
+-0b871e271c4c620444f8264f72143b4d224aa305306d85dd77ab8dce785b1e85 src/binutils-2.29.tar.xz
+ 87b565e89a9a684fe4ebeeddb8399dce2599f9c9049854ca8c0dfbdea0e21912 src/gmp-6.1.2.tar.xz
+-617decc6ea09889fb08ede330917a00b16809b8db88c29c31bfbb49cbf88ecc3 src/mpc-1.0.3.tar.gz
+-7a62ac1a04408614fccdc506e4844b10cf0ad2c2b1677097f8f35d3a1344a950 src/mpfr-3.1.6.tar.xz
+ 6985c538143c1208dcb1ac42cedad6ff52e267b47e5f970183a3e75125b43c2e src/mpc-1.1.0.tar.gz
+-fbe2cd1418b321f5c899ce4f0f0f4e73f5ecc7d02145b0e1fd096f5c3afb8a1d src/mpfr-4.0.0.tar.xz
+-c8566335ee74e5fcaeb8595b4ebd0400c4b043d6acb3263ecb1314f8f5501332 src/newlib-3.0.0.tar.gz
+-832ca6ae04636adbb430e865a1451adf6979ab44ca1c8374f61fba65645ce15c src/gcc-7.3.0.tar.xz
+-e7010a46969f9d3e53b650a518663f98a5dde3c3ae21b7d71e5e6803bc36b577 src/binutils-2.29.1.tar.xz
+-67874a60826303ee2fb6affc6dc0ddd3e749e9bfcb4c8655e3953d0458a6e16e src/mpfr-4.0.1.tar.xz
+-6e46b8aeae2f727a36f0bd9505e405768a72218f1796f0d09757d45209871ae6 src/binutils-2.30.tar.xz
+-1d1866f992626e61349a1ccd0b8d5253816222cdc13390dcfaa74b093aa2b153 src/gcc-8.1.0.tar.xz
+-5d20086ecf5752cc7d9134246e9588fa201740d540f7eb84d795b1f7a93bca86 src/binutils-2.31.1.tar.xz
+-196c3c04ba2613f893283977e6011b2345d1cd1af9abeac58e916b1aab3e0080 src/gcc-8.2.0.tar.xz
+\ No newline at end of file
++fb4fa1cc21e9060719208300a61420e4089d6de6ef59cf533b57fe74801d102a src/newlib-3.1.0.tar.gz
++1d3be708604eae0e42d578ba93b390c2a145f17743a744d8f3f8c2ad5855a38a src/mpfr-4.0.2.tar.xz
++0ab6c55dd86a92ed561972ba15b9b70a8b9f75557f896446c82e8b36e473ee04 src/binutils-2.32.tar.xz
++79a66834e96a6050d8fe78db2c3b32fb285b230b855d0a66288235bc04b327a0 src/gcc-9.1.0.tar.xz
+--
+2.26.0
+