[][openwrt-24][common][config][Rewrite defconfig manipulation of unified autobuild]

[Description]
Refactor defconfig manipulation of unified autobuild

[Release-log]
N/A

Change-Id: If8149cbe29559d8be61f74b475f1aff4df9cb1ae
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/9684126
diff --git a/autobuild/unified/rules b/autobuild/unified/rules
index 45477bb..d6a2735 100755
--- a/autobuild/unified/rules
+++ b/autobuild/unified/rules
@@ -5,7 +5,7 @@
 # Top rules
 
 # Declare substages of every stage
-sub_stage_prepare="update_ab_info mk_ab_tmp pre_prepare apply_feed_script_patch modify_feeds_conf update_feeds \
+sub_stage_prepare="update_ab_info mk_ab_tmp pre_prepare apply_autobuild_script_patch modify_feeds_conf update_feeds \
 		   mtk_feed_prepare install_feeds autobuild_prepare post_prepare"
 sub_stage_build="update_ab_info pre_build do_build post_build"
 sub_stage_release="pre_release do_release post_release"
@@ -114,9 +114,12 @@
 	exec_log "mkdir -p \"${ab_tmp}\""
 }
 
-# Modify scripts/feed to allow specify subdirectory for scanning
-apply_feed_script_patch() {
-	apply_patch "${ab_root}/scripts/${openwrt_branch}/scripts-feeds-support-subdir.patch"
+# Modify scripts for specific features:
+# 1. allow specify feed subdirectory for scanning
+# 2. allow config merging and diff
+apply_autobuild_script_patch() {
+	copy_files "${ab_root}/scripts/${openwrt_branch}/files"
+	apply_patches "${ab_root}/scripts/${openwrt_branch}/patches"
 }
 
 modify_feeds_conf() {
@@ -293,50 +296,32 @@
 # prepare_wifi_driver() { } Implemented by wifi inclusion
 
 prepare_openwrt_config() {
-	local kconfig_expr=
-	local kconfig_files="\"${ab_platform_dir}/${openwrt_branch}/defconfig\""
+	local kconfig_files=
+
+	exec_log "make -C \"${openwrt_root}\" prereq scripts/config/aconf"
 
 	openwrt_config_file="${openwrt_root}/.config"
 
+	kconfig_files="\"${ab_platform_dir}/${openwrt_branch}/defconfig\""
+
 	if test ${ab_branch_level} -ge 2; then
-		kconfig_expr="${kconfig_expr} +"
 		kconfig_files="${kconfig_files} \"${ab_wifi_dir}/${openwrt_branch}/defconfig\""
-
-		if test -f "${ab_wifi_dir}/${openwrt_branch}/defconfig_forced"; then
-			kconfig_expr="${kconfig_expr} +"
-			kconfig_files="${kconfig_files} \"${ab_wifi_dir}/${openwrt_branch}/defconfig_forced\""
-		fi
 	fi
 
 	if test ${ab_branch_level} -ge 3; then
-		kconfig_expr="${kconfig_expr} +"
 		kconfig_files="${kconfig_files} \"${ab_sku_dir}/${openwrt_branch}/defconfig\""
-
-		if test -f "${ab_sku_dir}/${openwrt_branch}/defconfig_forced"; then
-			kconfig_expr="${kconfig_expr} +"
-			kconfig_files="${kconfig_files} \"${ab_sku_dir}/${openwrt_branch}/defconfig_forced\""
-		fi
 	fi
 
 	if test ${ab_branch_level} -ge 4; then
-		kconfig_expr="${kconfig_expr} +"
 		kconfig_files="${kconfig_files} \"${ab_variant_dir}/${openwrt_branch}/defconfig\""
-
-		if test -f "${ab_variant_dir}/${openwrt_branch}/defconfig_forced"; then
-			kconfig_expr="${kconfig_expr} +"
-			kconfig_files="${kconfig_files} \"${ab_variant_dir}/${openwrt_branch}/defconfig_forced\""
-		fi
 	fi
 
-	if test -n "${kconfig_expr}" -a -n "${kconfig_files}"; then
-		exec_log "\"${openwrt_root}/scripts/kconfig.pl\" ${kconfig_expr} ${kconfig_files} > \"${ab_tmp}/.config\"" 1
-	else
-		exec_log "cp ${kconfig_files} \"${ab_tmp}/.config\""
-	fi
+	exec_log "rm -f \"${openwrt_config_file}.old\""
+	exec_log "STAGING_DIR_HOST=\"${openwrt_root}/staging_dir/host\" \"${openwrt_root}/scripts/config/aconf\" -m -o \"${openwrt_config_file}\" -k \"${openwrt_root}/Config.in\" ${kconfig_files}"
 
-	exec_log "rm -f \"${openwrt_root}/.config.old\""
-	exec_log "make -C \"${openwrt_root}\" -f \"${ab_root}/scripts/openwrt_kconfig.mk\" loaddefconfig CONFIG_FILE=\"${ab_tmp}/.config\""
-	exec_log "cp -f \"${openwrt_root}/.config\" \"${ab_tmp}/.config.prev\""
+	# for debug purpose
+	exec_log "cp -f \"${openwrt_config_file}\" \"${ab_tmp}/${ab_branch}.config\""
+	exec_log "STAGING_DIR_HOST=\"${openwrt_root}/staging_dir/host\" \"${openwrt_root}/scripts/config/aconf\" -m -M -o \"${ab_tmp}/${ab_branch}_defconfig\" -k \"${openwrt_root}/Config.in\" ${kconfig_files}"
 }
 
 # {platform,wifi,sku,variant}_change_openwrt_config() { } Implemented by platform sub levels
@@ -545,57 +530,32 @@
 }
 
 do_menuconfig_update() {
-	local kconfig_expr=
 	local kconfig_files=
 	local final_file=
 
-	exec_log "make -C \"${openwrt_root}\" menuconfig"
+	openwrt_config_file="${openwrt_root}/.config"
+
+	exec_log "make -C \"${openwrt_root}\" menuconfig scripts/config/aconf"
 
 	if test ${ab_branch_level} -eq 1; then
-		exec_log "make -C ${openwrt_root} -f \"${ab_root}/scripts/openwrt_kconfig.mk\" savedefconfig CONFIG_FILE=\"${ab_tmp}/defconfig\""
-		exec_log "\"${openwrt_root}/scripts/kconfig.pl\" + \"${ab_tmp}/defconfig\" /dev/null > \"${ab_platform_dir}/${openwrt_branch}/defconfig\"" 1
+		exec_log "STAGING_DIR_HOST=\"${openwrt_root}/staging_dir/host\" \"${openwrt_root}/scripts/config/aconf\" -m -M -o \"${ab_platform_dir}/${openwrt_branch}/defconfig\" -k \"${openwrt_root}/Config.in\" \"${openwrt_config_file}\""
 		return
 	fi
 
-	exec_log "make -C ${openwrt_root} -f \"${ab_root}/scripts/openwrt_kconfig.mk\" savedefconfig CONFIG_FILE=\"${ab_tmp}/defconfig\""
-
 	if test ${ab_branch_level} -ge 2; then
 		kconfig_files="\"${ab_platform_dir}/${openwrt_branch}/defconfig\""
 		final_file="${ab_wifi_dir}/${openwrt_branch}/defconfig"
-
-		if test -f "${ab_wifi_dir}/${openwrt_branch}/defconfig_forced"; then
-			kconfig_expr="${kconfig_expr} +"
-			kconfig_files="${kconfig_files} \"${ab_wifi_dir}/${openwrt_branch}/defconfig_forced\""
-		fi
 	fi
 
 	if test ${ab_branch_level} -ge 3; then
-		kconfig_expr="${kconfig_expr} +"
 		kconfig_files="${kconfig_files} \"${final_file}\""
 		final_file="${ab_sku_dir}/${openwrt_branch}/defconfig"
-
-		if test -f "${ab_sku_dir}/${openwrt_branch}/defconfig_forced"; then
-			kconfig_expr="${kconfig_expr} +"
-			kconfig_files="${kconfig_files} \"${ab_sku_dir}/${openwrt_branch}/defconfig_forced\""
-		fi
 	fi
 
 	if test ${ab_branch_level} -ge 4; then
-		kconfig_expr="${kconfig_expr} +"
 		kconfig_files="${kconfig_files} \"${final_file}\""
 		final_file="${ab_variant_dir}/${openwrt_branch}/defconfig"
-
-		if test -f "${ab_variant_dir}/${openwrt_branch}/defconfig_forced"; then
-			kconfig_expr="${kconfig_expr} +"
-			kconfig_files="${kconfig_files} \"${ab_variant_dir}/${openwrt_branch}/defconfig_forced\""
-		fi
-	fi
-
-	if test -n "${kconfig_expr}" -a -n "${kconfig_files}"; then
-		exec_log "\"${openwrt_root}/scripts/kconfig.pl\" ${kconfig_expr} ${kconfig_files} > \"${ab_tmp}/.config.base\"" 1
-	else
-		exec_log "cp ${kconfig_files} \"${ab_tmp}/.config.base\""
 	fi
 
-	exec_log "\"${openwrt_root}/scripts/kconfig.pl\" - \"${ab_tmp}/defconfig\" \"${ab_tmp}/.config.base\" > \"${final_file}\"" 1
+	exec_log "STAGING_DIR_HOST=\"${openwrt_root}/staging_dir/host\" \"${openwrt_root}/scripts/config/aconf\" -d -o \"${final_file}\" -k \"${openwrt_root}/Config.in\" -n \"${openwrt_config_file}\" ${kconfig_files}"
 }
diff --git a/autobuild/unified/scripts/21.02/files/scripts/config/aconf.c b/autobuild/unified/scripts/21.02/files/scripts/config/aconf.c
new file mode 100644
index 0000000..cb0995c
--- /dev/null
+++ b/autobuild/unified/scripts/21.02/files/scripts/config/aconf.c
@@ -0,0 +1,800 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Copyright (C) 2024 Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include "lkc.h"
+
+#define CONF_SYM_HASH_SIZE		1024
+
+struct conf_sym_info {
+	struct list_head node;
+	char *name;
+	char *val;
+};
+
+struct conf_sym_map {
+	struct list_head hash_table[CONF_SYM_HASH_SIZE];
+};
+
+static bool merge_mode, diff_mode, min_mode;
+static char *out_file, *kconfig_file, *new_file;
+
+static const struct option long_opts[] = {
+	{"help",          no_argument,       NULL,            'h'},
+	{"merge",         no_argument,       NULL,            'm'},
+	{"diff",          no_argument,       NULL,            'd'},
+	{"min",           no_argument,       NULL,            'M'},
+	{"out",           required_argument, NULL,            'o'},
+	{"kconfig",       required_argument, NULL,            'k'},
+	{"new",           required_argument, NULL,            'n'},
+	{NULL, 0, NULL, 0}
+};
+
+static void conf_usage(const char *progname)
+{
+	printf("Kconfig merging and diff tool\n");
+	printf("Usage: %s [options] <config file [config file ...]>\n", progname);
+	printf("\n");
+	printf("Options:\n");
+	printf("  -h, --help                   Print this message and exit.\n");
+	printf("  -m, --merge                  Merge config files\n");
+	printf("  -d, --diff                   Create config diff file\n");
+	printf("  -M, --min                    Write minimum defconfig (merge mode only)\n");
+	printf("  -o <file>, --out <file>      Specify output config file\n");
+	printf("  -k <file>, --kconfig <file>  Specify Kconfig file\n");
+	printf("  -n <file>, --new <file>      Specify new config file for diff\n");
+	printf("\n");
+	printf("Merge mode: merge all config files to output config file\n");
+	printf("Diff mode:  merge all config files as \"old\" config file,\n");
+	printf("            then write diff configs between \"old\" and new config file\n");
+	printf("            to output config file.\n");
+	printf("            The output config file can be merged with \"old\" config file\n");
+	printf("            to generate the new config file.\n");
+}
+
+static void list_head_init(struct list_head *head)
+{
+	head->next = head;
+	head->prev = head;
+}
+
+static struct conf_sym_map *conf_map_new(void)
+{
+	struct conf_sym_map *csm;
+	int i;
+
+	csm = calloc(1, sizeof(struct conf_sym_map));
+	if (!csm)
+		return NULL;
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++)
+		list_head_init(&csm->hash_table[i]);
+
+	return csm;
+}
+
+static void conf_map_free(struct conf_sym_map *csm)
+{
+	struct conf_sym_info *csi, *tmp;
+	int i;
+
+	if (!csm)
+		return;
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++) {
+		list_for_each_entry_safe(csi, tmp, &csm->hash_table[i], node) {
+			list_del(&csi->node);
+			free(csi->name);
+			if (csi->val)
+				free(csi->val);
+			free(csi);
+		}
+	}
+
+	free(csm);
+}
+
+static struct conf_sym_info *conf_map_find(struct conf_sym_map *csm,
+					   const char *name)
+{
+	struct conf_sym_info *csi;
+	int hash;
+
+	if (!csm || !name)
+		return NULL;
+
+	hash = strhash(name) % CONF_SYM_HASH_SIZE;
+
+	list_for_each_entry(csi, &csm->hash_table[hash], node) {
+		if (!strcmp(csi->name, name))
+			return csi;
+	}
+
+	return NULL;
+}
+
+static void conf_map_set(struct conf_sym_map *csm, const char *name,
+			 const char *val)
+{
+	struct conf_sym_info *csi;
+	int hash;
+
+	if (!csm || !name)
+		return;
+
+	if (!val)
+		val = "";
+
+	csi = conf_map_find(csm, name);
+	if (csi) {
+		if (csi->val) {
+			free(csi->val);
+			csi->val = NULL;
+		}
+
+		if (val)
+			csi->val = xstrdup(val);
+		return;
+	}
+
+	csi = xcalloc(1, sizeof(*csi));
+	list_head_init(&csi->node);
+	csi->name = xstrdup(name);
+	if (val)
+		csi->val = xstrdup(val);
+
+	hash = strhash(name) % CONF_SYM_HASH_SIZE;
+
+	list_add_tail(&csi->node, &csm->hash_table[hash]);
+}
+
+static struct conf_sym_map *conf_map_dup(struct conf_sym_map *csm)
+{
+	struct conf_sym_info *csi, *csi_new;
+	struct conf_sym_map *csm_new;
+	int i;
+
+	if (!csm)
+		return conf_map_new();
+
+	csm_new = conf_map_new();
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++) {
+		list_for_each_entry(csi, &csm->hash_table[i], node) {
+			csi_new = xcalloc(1, sizeof(*csi_new));
+			list_head_init(&csi_new->node);
+			csi_new->name = xstrdup(csi->name);
+			if (csi->val)
+				csi_new->val = xstrdup(csi->val);
+			list_add_tail(&csi_new->node, &csm_new->hash_table[i]);
+		}
+	}
+
+	return csm_new;
+}
+
+static int conf_map_read(struct conf_sym_map *csm, const char *name)
+{
+	size_t line_asize = 0;
+	char *line = NULL;
+	char *p, *p2;
+	FILE *in;
+
+	in = zconf_fopen(name);
+	if (!in)
+		return 1;
+
+	while (compat_getline(&line, &line_asize, in) != -1) {
+		if (line[0] == '#') {
+			if (memcmp(line + 2, CONFIG_, strlen(CONFIG_)))
+				continue;
+			p = strchr(line + 2 + strlen(CONFIG_), ' ');
+			if (!p)
+				continue;
+			*p++ = 0;
+			if (strncmp(p, "is not set", 10))
+				continue;
+
+			conf_map_set(csm, line + 2 + strlen(CONFIG_), "n");
+		} else if (memcmp(line, CONFIG_, strlen(CONFIG_)) == 0) {
+			p = strchr(line + strlen(CONFIG_), '=');
+			if (!p)
+				continue;
+			*p++ = 0;
+			p2 = strchr(p, '\n');
+			if (p2) {
+				*p2-- = 0;
+				if (*p2 == '\r')
+					*p2 = 0;
+			}
+
+			conf_map_set(csm, line + strlen(CONFIG_), p);
+		}
+	}
+
+	free(line);
+	fclose(in);
+
+	return 0;
+}
+
+static int conf_map_read_multi(struct conf_sym_map *csm, char *files[],
+			       size_t count)
+{
+	size_t i;
+
+	for (i = 0; i < count; i++) {
+		if (conf_map_read(csm, files[i])) {
+			fprintf(stderr, "Unable to read config file '%s'\n",
+				files[i]);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static void conf_map_gen_snapshot(struct conf_sym_map *csm)
+{
+	struct symbol *sym;
+	struct menu *menu;
+	int i;
+
+	sym_clear_all_valid();
+
+	for_all_symbols(i, sym)
+		sym->flags &= ~SYMBOL_WRITTEN;
+
+	menu = rootmenu.list;
+	while (menu) {
+		sym = menu->sym;
+		if (sym) {
+			if (!(sym->flags & SYMBOL_CHOICE) &&
+			    !(sym->flags & SYMBOL_WRITTEN)) {
+				sym_calc_value(sym);
+				if (sym->flags & SYMBOL_WRITE) {
+					sym->flags |= SYMBOL_WRITTEN;
+
+					conf_map_set(csm, sym->name,
+						     sym_get_string_value(sym));
+				}
+			}
+		}
+
+		if (menu->list) {
+			menu = menu->list;
+			continue;
+		}
+
+end_check:
+		if (menu->next) {
+			menu = menu->next;
+		} else {
+			menu = menu->parent;
+			if (menu)
+				goto end_check;
+		}
+	}
+
+	for_all_symbols(i, sym)
+		sym->flags &= ~SYMBOL_WRITTEN;
+}
+
+static void conf_set_all_new_symbols_default(void)
+{
+	struct symbol *sym, *csym;
+	int i;
+
+	sym_clear_all_valid();
+
+	/*
+	 * We have different type of choice blocks.
+	 * If curr.tri equals to mod then we can select several
+	 * choice symbols in one block.
+	 * In this case we do nothing.
+	 * If curr.tri equals yes then only one symbol can be
+	 * selected in a choice block and we set it to yes,
+	 * and the rest to no.
+	 */
+	for_all_symbols(i, csym) {
+		if ((sym_is_choice(csym) && !sym_has_value(csym)) ||
+			sym_is_choice_value(csym))
+			csym->flags |= SYMBOL_NEED_SET_CHOICE_VALUES;
+	}
+
+	for_all_symbols(i, csym) {
+		if (sym_has_value(csym) || !sym_is_choice(csym))
+			continue;
+
+		sym_calc_value(csym);
+		set_all_choice_values(csym);
+	}
+}
+
+static int conf_set_raw(const char *name, const char *val, int def)
+{
+	struct symbol *sym;
+	int ret, def_flags;
+	char *s;
+
+	def_flags = SYMBOL_DEF << def;
+
+	sym = sym_find(name);
+	if (!sym)
+		return 0;
+
+	s = xstrdup(val);
+	ret = conf_set_sym_val(sym, def, def_flags, s);
+	free(s);
+
+	if (ret)
+		return 1;
+
+	if (sym && sym_is_choice_value(sym)) {
+		struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym));
+		switch (sym->def[def].tri) {
+		case no:
+			break;
+		case mod:
+			if (cs->def[def].tri == yes) {
+				cs->flags &= ~def_flags;
+			}
+			break;
+		case yes:
+			cs->def[def].val = sym;
+			break;
+		}
+		cs->def[def].tri = EXPR_OR(cs->def[def].tri, sym->def[def].tri);
+	}
+
+	return 0;
+}
+
+static void conf_set_raw_from_map(const struct conf_sym_map *csm)
+{
+	struct conf_sym_info *csi;
+	int i;
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++) {
+		list_for_each_entry(csi, &csm->hash_table[i], node) {
+			conf_set_raw(csi->name, csi->val, S_DEF_USER);
+		}
+	}
+}
+
+static int conf_finish_merge(void)
+{
+	int conf_unsaved = 0;
+	struct symbol *sym;
+	int i;
+
+	sym_calc_value(modules_sym);
+
+	for_all_symbols(i, sym) {
+		sym_calc_value(sym);
+		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 */
+			switch (sym->type) {
+			case S_BOOLEAN:
+			case S_TRISTATE:
+				if (sym->def[S_DEF_USER].tri == sym_get_tristate_value(sym))
+					continue;
+				break;
+			default:
+				if (!strcmp(sym->curr.val, sym->def[S_DEF_USER].val))
+					continue;
+				break;
+			}
+		} else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE))
+			/* no previous value and not saved */
+			continue;
+		conf_unsaved++;
+		/* maybe print value in verbose mode... */
+	}
+
+	for_all_symbols(i, sym) {
+		if (sym_has_value(sym) && !sym_is_choice_value(sym)) {
+			/* Reset values of generates values, so they'll appear
+			 * as new, if they should become visible, but that
+			 * doesn't quite work if the Kconfig and the saved
+			 * configuration disagree.
+			 */
+			if (sym->visible == no && !conf_unsaved)
+				sym->flags &= ~SYMBOL_DEF_USER;
+			switch (sym->type) {
+			case S_STRING:
+			case S_INT:
+			case S_HEX:
+				/* Reset a string value if it's out of range */
+				if (sym_string_within_range(sym, sym->def[S_DEF_USER].val))
+					break;
+				sym->flags &= ~(SYMBOL_VALID|SYMBOL_DEF_USER);
+				conf_unsaved++;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void conf_loaddef_from_map(struct conf_sym_map *csm_list[],
+				  size_t count)
+{
+	size_t i;
+
+	/* Perform load defconfig file */
+	sym_set_change_count(0);
+
+	conf_reset(S_DEF_USER);
+
+	for (i = 0; i < count; i++)
+		conf_set_raw_from_map(csm_list[i]);
+
+	conf_finish_merge();
+	conf_set_all_new_symbols_default();
+}
+
+static int conf_loaddef(char *files[], size_t count,
+			struct conf_sym_map **retcsm)
+{
+	struct conf_sym_map *csm;
+
+	csm = conf_map_new();
+
+	if (conf_map_read_multi(csm, files, count)) {
+		conf_map_free(csm);
+		return 1;
+	}
+
+	conf_loaddef_from_map(&csm, 1);
+
+	if (retcsm)
+		*retcsm = csm;
+	else
+		conf_map_free(csm);
+
+	return 0;
+}
+
+static int do_conf_merge(int argc, char *argv[])
+{
+	int ret;
+
+	if (conf_loaddef(argv, argc, NULL))
+		return 1;
+
+	if (min_mode) {
+		ret = conf_write_defconfig(out_file);
+		if (!ret) {
+			printf("#\n# ");
+			printf("minimum configuration written to %s", out_file);
+			printf("\n#\n");
+		}
+	} else {
+		setenv("KCONFIG_OVERWRITECONFIG", "1", 1);
+		ret = conf_write(out_file);
+	}
+
+	if (ret) {
+		fprintf(stderr, "\n*** Error during writing of the configuration.\n\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int conf_cmp(const char *s1, const char *s2)
+{
+	if ((s1 && !s2) || (!s1 && s2))
+		return -1;
+
+	if (!s1 && !s2)
+		return 0;
+
+	return strcmp(s1, s2);
+}
+
+static size_t conf_diff_to_map(struct conf_sym_map *csm,
+			       struct conf_sym_map *csm_old)
+{
+	const char *old_val, *new_val;
+	struct conf_sym_info *csi;
+	struct symbol *sym;
+	struct menu *menu;
+	size_t count = 0;
+
+	sym_clear_all_valid();
+
+	/* Traverse all menus to find all relevant symbols */
+	menu = rootmenu.list;
+
+	while (menu != NULL) {
+		sym = menu->sym;
+		if (sym == NULL) {
+			if (!menu_is_visible(menu))
+				goto next_menu;
+		} else if (!sym_is_choice(sym)) {
+			sym_calc_value(sym);
+			if (!(sym->flags & SYMBOL_WRITE))
+				goto next_menu;
+			sym->flags &= ~SYMBOL_WRITE;
+			/* If we cannot change the symbol - skip */
+			if (!sym_is_changeable(sym))
+				goto next_menu;
+			/* If symbol equals to old value - skip */
+			csi = conf_map_find(csm_old, sym->name);
+			if (!csi)
+				old_val = NULL;
+			else
+				old_val = csi->val;
+			new_val = sym_get_string_value(sym);
+
+			if (!conf_cmp(old_val, new_val))
+				goto next_menu;
+
+			if (sym_is_choice_value(sym)) {
+				struct symbol *cs;
+				struct symbol *ds;
+
+				cs = prop_get_symbol(sym_get_choice_prop(sym));
+				ds = sym_choice_default(cs);
+				if (!sym_is_optional(cs) && sym == ds) {
+					if ((sym->type == S_BOOLEAN) &&
+					    sym_get_tristate_value(sym) == yes)
+						goto next_menu;
+				}
+			}
+
+			conf_map_set(csm, sym->name, new_val);
+			count++;
+		}
+next_menu:
+		if (menu->list != NULL) {
+			menu = menu->list;
+		}
+		else if (menu->next != NULL) {
+			menu = menu->next;
+		} else {
+			while ((menu = menu->parent)) {
+				if (menu->next != NULL) {
+					menu = menu->next;
+					break;
+				}
+			}
+		}
+	}
+
+	return count;
+}
+
+static int conf_write_diff(struct conf_sym_map *csm_changed,
+			   struct conf_sym_map *csm_old,
+			   const char *filename)
+{
+	struct conf_sym_info *csi;
+	const char *val_curr;
+	struct symbol *sym;
+	struct menu *menu;
+	FILE *out;
+
+	out = fopen(filename, "w");
+	if (!out)
+		return 1;
+
+	sym_clear_all_valid();
+
+	/* Traverse all menus to find all relevant symbols */
+	menu = rootmenu.list;
+
+	while (menu != NULL) {
+		sym = menu->sym;
+		if (sym == NULL) {
+			if (!menu_is_visible(menu))
+				goto next_menu;
+		} else if (!sym_is_choice(sym)) {
+			sym_calc_value(sym);
+			if (!(sym->flags & SYMBOL_WRITE))
+				goto next_menu;
+			sym->flags &= ~SYMBOL_WRITE;
+			/* If we cannot change the symbol - skip */
+			if (!sym_is_changeable(sym))
+				goto next_menu;
+			/* If symbol not changed - skip */
+			if (!conf_map_find(csm_changed, sym->name))
+				goto next_menu;
+
+			/* If symbol equals to both default and "old" value - skip */
+			val_curr = sym_get_string_value(sym);
+			csi = conf_map_find(csm_old, sym->name);
+			if (!strcmp(val_curr, sym_get_string_default(sym)) &&
+			    (!csi || (csi && !strcmp(val_curr, csi->val))))
+				goto next_menu;
+
+			if (sym_is_choice_value(sym)) {
+				struct symbol *cs;
+				struct symbol *ds;
+
+				cs = prop_get_symbol(sym_get_choice_prop(sym));
+				ds = sym_choice_default(cs);
+				if (!sym_is_optional(cs) && sym == ds) {
+					if ((sym->type == S_BOOLEAN) &&
+					    sym_get_tristate_value(sym) == yes)
+						goto next_menu;
+				}
+			}
+
+			conf_write_symbol(out, sym, &kconfig_printer_cb, NULL);
+		}
+next_menu:
+		if (menu->list != NULL) {
+			menu = menu->list;
+		}
+		else if (menu->next != NULL) {
+			menu = menu->next;
+		} else {
+			while ((menu = menu->parent)) {
+				if (menu->next != NULL) {
+					menu = menu->next;
+					break;
+				}
+			}
+		}
+	}
+	fclose(out);
+	return 0;
+}
+
+static int do_conf_diff(int argc, char *argv[])
+{
+	struct conf_sym_map *csm_old, *csm_old_orig, *csm_old_min, *csm_new, *csm_changed, *list[2];
+	size_t n;
+	int ret;
+
+	csm_changed = conf_map_new();
+
+	/* Merge all config files as original "old" config */
+	if (conf_loaddef(argv, argc, &csm_old_min))
+		return 1;
+
+	/* Record all "old" config values */
+	csm_old = conf_map_new();
+	conf_map_gen_snapshot(csm_old);
+	csm_old_orig = conf_map_dup(csm_old);
+
+	/* Perform load defconfig for "new" config */
+	if (conf_loaddef(&new_file, 1, &csm_new))
+		return 1;
+
+	/* Calculates incremental symbols from original "old" to "new" config */
+	while (true) {
+		n = conf_diff_to_map(csm_changed, csm_old);
+		if (!n) {
+			/* Until there's no new changed symbol */
+			break;
+		}
+
+		/* Perform load defconfig for original "min" + current "diff" configs */
+		list[0] = csm_old_min;
+		list[1] = csm_changed;
+		conf_loaddef_from_map(list, 2);
+
+		/* Record all current "old" config values */
+		conf_map_free(csm_old);
+		csm_old = conf_map_new();
+		conf_map_gen_snapshot(csm_old);
+
+		/* Repeat performing load defconfig for "new" config */
+		conf_loaddef_from_map(&csm_new, 1);
+	}
+
+	/* Generate final defconfig file */
+	conf_write_diff(csm_changed, csm_old_orig, out_file);
+
+	printf("#\n# ");
+	printf("incremental configuration written to %s", out_file);
+	printf("\n#\n");
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	char *progname;
+	int opt;
+
+	progname = strrchr(argv[0], '/');
+	if (progname)
+		progname++;
+	else
+		progname = argv[0];
+
+	while ((opt = getopt_long(argc, argv, "hmdMo:k:n:", long_opts, NULL)) != -1) {
+		switch (opt) {
+		case 'h':
+			conf_usage(progname);
+			return 0;
+
+		case 'm':
+			merge_mode = true;
+			break;
+
+		case 'd':
+			diff_mode = true;
+			break;
+
+		case 'M':
+			min_mode = true;
+			break;
+
+		case 'o':
+			out_file = optarg;
+			break;
+
+		case 'k':
+			kconfig_file = optarg;
+			break;
+
+		case 'n':
+			new_file = optarg;
+			break;
+
+		default:;
+		}
+	}
+
+	if (optind == argc) {
+		fprintf(stderr, "%s: Missing config file\n", progname);
+		goto err_usage;
+	}
+
+	if (!merge_mode && !diff_mode) {
+		fprintf(stderr, "%s: Operation mode (merge or diff) not specified\n", progname);
+		goto err_usage;
+	}
+
+	if (merge_mode && diff_mode) {
+		fprintf(stderr, "%s: Only one operation mode can be specified\n", progname);
+		goto err_usage;
+	}
+
+	if (diff_mode && !new_file) {
+		fprintf(stderr, "%s: New config file not specified in diff mode\n", progname);
+		goto err_usage;
+	}
+
+	if (!out_file) {
+		fprintf(stderr, "%s: Output file not specified\n", progname);
+		goto err_usage;
+	}
+
+	if (!kconfig_file) {
+		fprintf(stderr, "%s: Kconfig file not specified\n", progname);
+		goto err_usage;
+	}
+
+	conf_parse(kconfig_file);
+
+	if (merge_mode)
+		return do_conf_merge(argc - optind, argv + optind);
+
+	return do_conf_diff(argc - optind, argv + optind);
+
+err_usage:
+	conf_usage(progname);
+
+	return 1;
+}
diff --git a/autobuild/unified/scripts/21.02/scripts-feeds-support-subdir.patch b/autobuild/unified/scripts/21.02/patches/0001-scripts-feeds-support-subdir.patch
similarity index 100%
rename from autobuild/unified/scripts/21.02/scripts-feeds-support-subdir.patch
rename to autobuild/unified/scripts/21.02/patches/0001-scripts-feeds-support-subdir.patch
diff --git a/autobuild/unified/scripts/21.02/patches/0002-scripts-kconfig-add-aconf.patch b/autobuild/unified/scripts/21.02/patches/0002-scripts-kconfig-add-aconf.patch
new file mode 100644
index 0000000..1e69a31
--- /dev/null
+++ b/autobuild/unified/scripts/21.02/patches/0002-scripts-kconfig-add-aconf.patch
@@ -0,0 +1,98 @@
+--- a/scripts/config/Makefile
++++ b/scripts/config/Makefile
+@@ -90,6 +90,10 @@ quiet_cmd_moc = MOC     $@
+ $(obj)/%.moc: $(src)/%.h $(obj)/qconf-cfg
+ 	$(call cmd,moc)
+ 
++# aconf: Used for defconfig merging and incremental subtraction
++hostprogs-y	+= aconf
++aconf-objs	:= aconf.o $(common-objs)
++
+ # check if necessary packages are available, and configure build flags
+ filechk_conf_cfg = $(CONFIG_SHELL) $<
+ 
+@@ -138,3 +142,5 @@ nconf: nconf-cfg $(nconf-objs)
+ 
+ qconf: qconf-cfg $(qconf-cxxobjs) $(qconf-objs)
+ 	$(CXX) -o $@ $(filter-out qconf-cfg,$^) $(HOSTLDLIBS_qconf)
++
++aconf: $(aconf-objs)
+--- a/scripts/config/confdata.c
++++ b/scripts/config/confdata.c
+@@ -231,7 +231,7 @@ static const char *conf_get_autoconfig_name(void)
+ 	return name ? name : "include/config/auto.conf";
+ }
+ 
+-static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
++int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
+ {
+ 	char *p2;
+ 
+@@ -313,7 +313,7 @@ static int add_byte(int c, char **lineptr, size_t slen, size_t *n)
+ 	return 0;
+ }
+ 
+-static ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream)
++ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream)
+ {
+ 	char *line = *lineptr;
+ 	size_t slen = 0;
+@@ -633,7 +633,7 @@ kconfig_print_comment(FILE *fp, const char *value, void *arg)
+ 	}
+ }
+ 
+-static struct conf_printer kconfig_printer_cb =
++struct conf_printer kconfig_printer_cb =
+ {
+ 	.print_symbol = kconfig_print_symbol,
+ 	.print_comment = kconfig_print_comment,
+@@ -713,8 +713,8 @@ static struct conf_printer header_printer_cb =
+ 	.print_comment = header_print_comment,
+ };
+ 
+-static void conf_write_symbol(FILE *fp, struct symbol *sym,
+-			      struct conf_printer *printer, void *printer_arg)
++void conf_write_symbol(FILE *fp, struct symbol *sym,
++		       struct conf_printer *printer, void *printer_arg)
+ {
+ 	const char *str;
+ 
+--- a/scripts/config/lkc.h
++++ b/scripts/config/lkc.h
+@@ -51,12 +51,17 @@ const char *zconf_curname(void);
+ extern int recursive_is_error;
+ 
+ /* confdata.c */
++extern struct conf_printer kconfig_printer_cb;
+ const char *conf_get_configname(void);
+ void sym_set_change_count(int count);
+ void sym_add_change_count(int count);
+ bool conf_set_all_new_symbols(enum conf_def_mode mode);
+ void conf_rewrite_mod_or_yes(enum conf_def_mode mode);
+ void set_all_choice_values(struct symbol *csym);
++int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p);
++ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream);
++void conf_write_symbol(FILE *fp, struct symbol *sym,
++		       struct conf_printer *printer, void *printer_arg);
+ 
+ /* confdata.c and expr.c */
+ static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out)
+@@ -118,6 +123,7 @@ 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 symbol *prop_get_symbol(struct property *prop);
++unsigned strhash(const char *s);
+ 
+ static inline tristate sym_get_tristate_value(struct symbol *sym)
+ {
+--- a/scripts/config/symbol.c
++++ b/scripts/config/symbol.c
+@@ -770,7 +770,7 @@ bool sym_is_changeable(struct symbol *sym)
+ 	return sym->visible > sym->rev_dep.tri;
+ }
+ 
+-static unsigned strhash(const char *s)
++unsigned strhash(const char *s)
+ {
+ 	/* fnv32 hash */
+ 	unsigned hash = 2166136261U;
diff --git a/autobuild/unified/scripts/master/files/scripts/config/aconf.c b/autobuild/unified/scripts/master/files/scripts/config/aconf.c
new file mode 100644
index 0000000..f685471
--- /dev/null
+++ b/autobuild/unified/scripts/master/files/scripts/config/aconf.c
@@ -0,0 +1,799 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
+ * Copyright (C) 2024 Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+#include "lkc.h"
+
+#define CONF_SYM_HASH_SIZE		1024
+
+struct conf_sym_info {
+	struct list_head node;
+	char *name;
+	char *val;
+};
+
+struct conf_sym_map {
+	struct list_head hash_table[CONF_SYM_HASH_SIZE];
+};
+
+static bool merge_mode, diff_mode, min_mode;
+static char *out_file, *kconfig_file, *new_file;
+
+static const struct option long_opts[] = {
+	{"help",          no_argument,       NULL,            'h'},
+	{"merge",         no_argument,       NULL,            'm'},
+	{"diff",          no_argument,       NULL,            'd'},
+	{"min",           no_argument,       NULL,            'M'},
+	{"out",           required_argument, NULL,            'o'},
+	{"kconfig",       required_argument, NULL,            'k'},
+	{"new",           required_argument, NULL,            'n'},
+	{NULL, 0, NULL, 0}
+};
+
+static void conf_usage(const char *progname)
+{
+	printf("Kconfig merging and diff tool\n");
+	printf("Usage: %s [options] <config file [config file ...]>\n", progname);
+	printf("\n");
+	printf("Options:\n");
+	printf("  -h, --help                   Print this message and exit.\n");
+	printf("  -m, --merge                  Merge config files\n");
+	printf("  -d, --diff                   Create config diff file\n");
+	printf("  -M, --min                    Write minimum defconfig (merge mode only)\n");
+	printf("  -o <file>, --out <file>      Specify output config file\n");
+	printf("  -k <file>, --kconfig <file>  Specify Kconfig file\n");
+	printf("  -n <file>, --new <file>      Specify new config file for diff\n");
+	printf("\n");
+	printf("Merge mode: merge all config files to output config file\n");
+	printf("Diff mode:  merge all config files as \"old\" config file,\n");
+	printf("            then write diff configs between \"old\" and new config file\n");
+	printf("            to output config file.\n");
+	printf("            The output config file can be merged with \"old\" config file\n");
+	printf("            to generate the new config file.\n");
+}
+
+static void list_head_init(struct list_head *head)
+{
+	head->next = head;
+	head->prev = head;
+}
+
+static struct conf_sym_map *conf_map_new(void)
+{
+	struct conf_sym_map *csm;
+	int i;
+
+	csm = calloc(1, sizeof(struct conf_sym_map));
+	if (!csm)
+		return NULL;
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++)
+		list_head_init(&csm->hash_table[i]);
+
+	return csm;
+}
+
+static void conf_map_free(struct conf_sym_map *csm)
+{
+	struct conf_sym_info *csi, *tmp;
+	int i;
+
+	if (!csm)
+		return;
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++) {
+		list_for_each_entry_safe(csi, tmp, &csm->hash_table[i], node) {
+			list_del(&csi->node);
+			free(csi->name);
+			if (csi->val)
+				free(csi->val);
+			free(csi);
+		}
+	}
+
+	free(csm);
+}
+
+static struct conf_sym_info *conf_map_find(struct conf_sym_map *csm,
+					   const char *name)
+{
+	struct conf_sym_info *csi;
+	int hash;
+
+	if (!csm || !name)
+		return NULL;
+
+	hash = strhash(name) % CONF_SYM_HASH_SIZE;
+
+	list_for_each_entry(csi, &csm->hash_table[hash], node) {
+		if (!strcmp(csi->name, name))
+			return csi;
+	}
+
+	return NULL;
+}
+
+static void conf_map_set(struct conf_sym_map *csm, const char *name,
+			 const char *val)
+{
+	struct conf_sym_info *csi;
+	int hash;
+
+	if (!csm || !name)
+		return;
+
+	if (!val)
+		val = "";
+
+	csi = conf_map_find(csm, name);
+	if (csi) {
+		if (csi->val) {
+			free(csi->val);
+			csi->val = NULL;
+		}
+
+		if (val)
+			csi->val = xstrdup(val);
+		return;
+	}
+
+	csi = xcalloc(1, sizeof(*csi));
+	list_head_init(&csi->node);
+	csi->name = xstrdup(name);
+	if (val)
+		csi->val = xstrdup(val);
+
+	hash = strhash(name) % CONF_SYM_HASH_SIZE;
+
+	list_add_tail(&csi->node, &csm->hash_table[hash]);
+}
+
+static struct conf_sym_map *conf_map_dup(struct conf_sym_map *csm)
+{
+	struct conf_sym_info *csi, *csi_new;
+	struct conf_sym_map *csm_new;
+	int i;
+
+	if (!csm)
+		return conf_map_new();
+
+	csm_new = conf_map_new();
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++) {
+		list_for_each_entry(csi, &csm->hash_table[i], node) {
+			csi_new = xcalloc(1, sizeof(*csi_new));
+			list_head_init(&csi_new->node);
+			csi_new->name = xstrdup(csi->name);
+			if (csi->val)
+				csi_new->val = xstrdup(csi->val);
+			list_add_tail(&csi_new->node, &csm_new->hash_table[i]);
+		}
+	}
+
+	return csm_new;
+}
+
+static int conf_map_read(struct conf_sym_map *csm, const char *name)
+{
+	size_t line_asize = 0;
+	char *line = NULL;
+	char *p, *p2;
+	FILE *in;
+
+	in = zconf_fopen(name);
+	if (!in)
+		return 1;
+
+	while (compat_getline(&line, &line_asize, in) != -1) {
+		if (line[0] == '#') {
+			if (memcmp(line + 2, CONFIG_, strlen(CONFIG_)))
+				continue;
+			p = strchr(line + 2 + strlen(CONFIG_), ' ');
+			if (!p)
+				continue;
+			*p++ = 0;
+			if (strncmp(p, "is not set", 10))
+				continue;
+
+			conf_map_set(csm, line + 2 + strlen(CONFIG_), "n");
+		} else if (memcmp(line, CONFIG_, strlen(CONFIG_)) == 0) {
+			p = strchr(line + strlen(CONFIG_), '=');
+			if (!p)
+				continue;
+			*p++ = 0;
+			p2 = strchr(p, '\n');
+			if (p2) {
+				*p2-- = 0;
+				if (*p2 == '\r')
+					*p2 = 0;
+			}
+
+			conf_map_set(csm, line + strlen(CONFIG_), p);
+		}
+	}
+
+	free(line);
+	fclose(in);
+
+	return 0;
+}
+
+static int conf_map_read_multi(struct conf_sym_map *csm, char *files[],
+			       size_t count)
+{
+	size_t i;
+
+	for (i = 0; i < count; i++) {
+		if (conf_map_read(csm, files[i])) {
+			fprintf(stderr, "Unable to read config file '%s'\n",
+				files[i]);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static void conf_map_gen_snapshot(struct conf_sym_map *csm)
+{
+	struct symbol *sym;
+	struct menu *menu;
+	int i;
+
+	sym_clear_all_valid();
+
+	for_all_symbols(i, sym)
+		sym->flags &= ~SYMBOL_WRITTEN;
+
+	menu = rootmenu.list;
+	while (menu) {
+		sym = menu->sym;
+		if (sym) {
+			if (!(sym->flags & SYMBOL_CHOICE) &&
+			    !(sym->flags & SYMBOL_WRITTEN)) {
+				sym_calc_value(sym);
+				if (sym->flags & SYMBOL_WRITE) {
+					sym->flags |= SYMBOL_WRITTEN;
+
+					conf_map_set(csm, sym->name,
+						     sym_get_string_value(sym));
+				}
+			}
+		}
+
+		if (menu->list) {
+			menu = menu->list;
+			continue;
+		}
+
+end_check:
+		if (menu->next) {
+			menu = menu->next;
+		} else {
+			menu = menu->parent;
+			if (menu)
+				goto end_check;
+		}
+	}
+
+	for_all_symbols(i, sym)
+		sym->flags &= ~SYMBOL_WRITTEN;
+}
+
+static void conf_set_all_new_symbols_default(void)
+{
+	struct symbol *sym, *csym;
+	int i;
+
+	sym_clear_all_valid();
+
+	/*
+	 * We have different type of choice blocks.
+	 * If curr.tri equals to mod then we can select several
+	 * choice symbols in one block.
+	 * In this case we do nothing.
+	 * If curr.tri equals yes then only one symbol can be
+	 * selected in a choice block and we set it to yes,
+	 * and the rest to no.
+	 */
+	for_all_symbols(i, csym) {
+		if ((sym_is_choice(csym) && !sym_has_value(csym)) ||
+			sym_is_choice_value(csym))
+			csym->flags |= SYMBOL_NEED_SET_CHOICE_VALUES;
+	}
+
+	for_all_symbols(i, csym) {
+		if (sym_has_value(csym) || !sym_is_choice(csym))
+			continue;
+
+		sym_calc_value(csym);
+		set_all_choice_values(csym);
+	}
+}
+
+static int conf_set_raw(const char *name, const char *val, int def)
+{
+	struct symbol *sym;
+	int ret, def_flags;
+	char *s;
+
+	def_flags = SYMBOL_DEF << def;
+
+	sym = sym_find(name);
+	if (!sym)
+		return 0;
+
+	s = xstrdup(val);
+	ret = conf_set_sym_val(sym, def, def_flags, s);
+	free(s);
+
+	if (ret)
+		return 1;
+
+	if (sym && sym_is_choice_value(sym)) {
+		struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym));
+		switch (sym->def[def].tri) {
+		case no:
+			break;
+		case mod:
+			if (cs->def[def].tri == yes) {
+				cs->flags &= ~def_flags;
+			}
+			break;
+		case yes:
+			cs->def[def].val = sym;
+			break;
+		}
+		cs->def[def].tri = EXPR_OR(cs->def[def].tri, sym->def[def].tri);
+	}
+
+	return 0;
+}
+
+static void conf_set_raw_from_map(const struct conf_sym_map *csm)
+{
+	struct conf_sym_info *csi;
+	int i;
+
+	for (i = 0; i < CONF_SYM_HASH_SIZE; i++) {
+		list_for_each_entry(csi, &csm->hash_table[i], node) {
+			conf_set_raw(csi->name, csi->val, S_DEF_USER);
+		}
+	}
+}
+
+static int conf_finish_merge(void)
+{
+	int conf_unsaved = 0;
+	struct symbol *sym;
+	int i;
+
+	sym_calc_value(modules_sym);
+
+	for_all_symbols(i, sym) {
+		sym_calc_value(sym);
+		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 */
+			switch (sym->type) {
+			case S_BOOLEAN:
+			case S_TRISTATE:
+				if (sym->def[S_DEF_USER].tri == sym_get_tristate_value(sym))
+					continue;
+				break;
+			default:
+				if (!strcmp(sym->curr.val, sym->def[S_DEF_USER].val))
+					continue;
+				break;
+			}
+		} else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE))
+			/* no previous value and not saved */
+			continue;
+		conf_unsaved++;
+		/* maybe print value in verbose mode... */
+	}
+
+	for_all_symbols(i, sym) {
+		if (sym_has_value(sym) && !sym_is_choice_value(sym)) {
+			/* Reset values of generates values, so they'll appear
+			 * as new, if they should become visible, but that
+			 * doesn't quite work if the Kconfig and the saved
+			 * configuration disagree.
+			 */
+			if (sym->visible == no && !conf_unsaved)
+				sym->flags &= ~SYMBOL_DEF_USER;
+			switch (sym->type) {
+			case S_STRING:
+			case S_INT:
+			case S_HEX:
+				/* Reset a string value if it's out of range */
+				if (sym_string_within_range(sym, sym->def[S_DEF_USER].val))
+					break;
+				sym->flags &= ~(SYMBOL_VALID|SYMBOL_DEF_USER);
+				conf_unsaved++;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void conf_loaddef_from_map(struct conf_sym_map *csm_list[],
+				  size_t count)
+{
+	size_t i;
+
+	/* Perform load defconfig file */
+	conf_set_changed(false);
+
+	conf_reset(S_DEF_USER);
+
+	for (i = 0; i < count; i++)
+		conf_set_raw_from_map(csm_list[i]);
+
+	conf_finish_merge();
+	conf_set_all_new_symbols_default();
+}
+
+static int conf_loaddef(char *files[], size_t count,
+			struct conf_sym_map **retcsm)
+{
+	struct conf_sym_map *csm;
+
+	csm = conf_map_new();
+
+	if (conf_map_read_multi(csm, files, count)) {
+		conf_map_free(csm);
+		return 1;
+	}
+
+	conf_loaddef_from_map(&csm, 1);
+
+	if (retcsm)
+		*retcsm = csm;
+	else
+		conf_map_free(csm);
+
+	return 0;
+}
+
+static int do_conf_merge(int argc, char *argv[])
+{
+	int ret;
+
+	if (conf_loaddef(argv, argc, NULL))
+		return 1;
+
+	if (min_mode) {
+		ret = conf_write_defconfig(out_file);
+		if (!ret) {
+			printf("#\n# ");
+			printf("minimum configuration written to %s", out_file);
+			printf("\n#\n");
+		}
+	} else {
+		setenv("KCONFIG_OVERWRITECONFIG", "1", 1);
+		ret = conf_write(out_file);
+	}
+
+	if (ret) {
+		fprintf(stderr, "\n*** Error during writing of the configuration.\n\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int conf_cmp(const char *s1, const char *s2)
+{
+	if ((s1 && !s2) || (!s1 && s2))
+		return -1;
+
+	if (!s1 && !s2)
+		return 0;
+
+	return strcmp(s1, s2);
+}
+
+static size_t conf_diff_to_map(struct conf_sym_map *csm,
+			       struct conf_sym_map *csm_old)
+{
+	const char *old_val, *new_val;
+	struct conf_sym_info *csi;
+	struct symbol *sym;
+	struct menu *menu;
+	size_t count = 0;
+
+	sym_clear_all_valid();
+
+	/* Traverse all menus to find all relevant symbols */
+	menu = rootmenu.list;
+
+	while (menu != NULL) {
+		sym = menu->sym;
+		if (sym == NULL) {
+			if (!menu_is_visible(menu))
+				goto next_menu;
+		} else if (!sym_is_choice(sym)) {
+			sym_calc_value(sym);
+			if (!(sym->flags & SYMBOL_WRITE))
+				goto next_menu;
+			sym->flags &= ~SYMBOL_WRITE;
+			/* If we cannot change the symbol - skip */
+			if (!sym_is_changeable(sym))
+				goto next_menu;
+			/* If symbol equals to old value - skip */
+			csi = conf_map_find(csm_old, sym->name);
+			if (!csi)
+				old_val = NULL;
+			else
+				old_val = csi->val;
+			new_val = sym_get_string_value(sym);
+
+			if (!conf_cmp(old_val, new_val))
+				goto next_menu;
+
+			if (sym_is_choice_value(sym)) {
+				struct symbol *cs;
+				struct symbol *ds;
+
+				cs = prop_get_symbol(sym_get_choice_prop(sym));
+				ds = sym_choice_default(cs);
+				if (!sym_is_optional(cs) && sym == ds) {
+					if ((sym->type == S_BOOLEAN) &&
+					    sym_get_tristate_value(sym) == yes)
+						goto next_menu;
+				}
+			}
+
+			conf_map_set(csm, sym->name, new_val);
+			count++;
+		}
+next_menu:
+		if (menu->list != NULL) {
+			menu = menu->list;
+		}
+		else if (menu->next != NULL) {
+			menu = menu->next;
+		} else {
+			while ((menu = menu->parent)) {
+				if (menu->next != NULL) {
+					menu = menu->next;
+					break;
+				}
+			}
+		}
+	}
+
+	return count;
+}
+
+static int conf_write_diff(struct conf_sym_map *csm_changed,
+			   struct conf_sym_map *csm_old,
+			   const char *filename)
+{
+	struct conf_sym_info *csi;
+	const char *val_curr;
+	struct symbol *sym;
+	struct menu *menu;
+	FILE *out;
+
+	out = fopen(filename, "w");
+	if (!out)
+		return 1;
+
+	sym_clear_all_valid();
+
+	/* Traverse all menus to find all relevant symbols */
+	menu = rootmenu.list;
+
+	while (menu != NULL) {
+		sym = menu->sym;
+		if (sym == NULL) {
+			if (!menu_is_visible(menu))
+				goto next_menu;
+		} else if (!sym_is_choice(sym)) {
+			sym_calc_value(sym);
+			if (!(sym->flags & SYMBOL_WRITE))
+				goto next_menu;
+			sym->flags &= ~SYMBOL_WRITE;
+			/* If we cannot change the symbol - skip */
+			if (!sym_is_changeable(sym))
+				goto next_menu;
+			/* If symbol not changed - skip */
+			if (!conf_map_find(csm_changed, sym->name))
+				goto next_menu;
+
+			/* If symbol equals to both default and "old" value - skip */
+			val_curr = sym_get_string_value(sym);
+			csi = conf_map_find(csm_old, sym->name);
+			if (!strcmp(val_curr, sym_get_string_default(sym)) &&
+			    (!csi || (csi && !strcmp(val_curr, csi->val))))
+				goto next_menu;
+
+			if (sym_is_choice_value(sym)) {
+				struct symbol *cs;
+				struct symbol *ds;
+
+				cs = prop_get_symbol(sym_get_choice_prop(sym));
+				ds = sym_choice_default(cs);
+				if (!sym_is_optional(cs) && sym == ds) {
+					if ((sym->type == S_BOOLEAN) &&
+					    sym_get_tristate_value(sym) == yes)
+						goto next_menu;
+				}
+			}
+
+			print_symbol_for_dotconfig(out, sym);
+		}
+next_menu:
+		if (menu->list != NULL) {
+			menu = menu->list;
+		}
+		else if (menu->next != NULL) {
+			menu = menu->next;
+		} else {
+			while ((menu = menu->parent)) {
+				if (menu->next != NULL) {
+					menu = menu->next;
+					break;
+				}
+			}
+		}
+	}
+	fclose(out);
+	return 0;
+}
+
+static int do_conf_diff(int argc, char *argv[])
+{
+	struct conf_sym_map *csm_old, *csm_old_orig, *csm_old_min, *csm_new, *csm_changed, *list[2];
+	size_t n;
+	int ret;
+
+	csm_changed = conf_map_new();
+
+	/* Merge all config files as original "old" config */
+	if (conf_loaddef(argv, argc, &csm_old_min))
+		return 1;
+
+	/* Record all "old" config values */
+	csm_old = conf_map_new();
+	conf_map_gen_snapshot(csm_old);
+	csm_old_orig = conf_map_dup(csm_old);
+
+	/* Perform load defconfig for "new" config */
+	if (conf_loaddef(&new_file, 1, &csm_new))
+		return 1;
+
+	/* Calculates incremental symbols from original "old" to "new" config */
+	while (true) {
+		n = conf_diff_to_map(csm_changed, csm_old);
+		if (!n) {
+			/* Until there's no new changed symbol */
+			break;
+		}
+
+		/* Perform load defconfig for original "min" + current "diff" configs */
+		list[0] = csm_old_min;
+		list[1] = csm_changed;
+		conf_loaddef_from_map(list, 2);
+
+		/* Record all current "old" config values */
+		conf_map_free(csm_old);
+		csm_old = conf_map_new();
+		conf_map_gen_snapshot(csm_old);
+
+		/* Repeat performing load defconfig for "new" config */
+		conf_loaddef_from_map(&csm_new, 1);
+	}
+
+	/* Generate final defconfig file */
+	conf_write_diff(csm_changed, csm_old_orig, out_file);
+
+	printf("#\n# ");
+	printf("incremental configuration written to %s", out_file);
+	printf("\n#\n");
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	char *progname;
+	int opt;
+
+	progname = strrchr(argv[0], '/');
+	if (progname)
+		progname++;
+	else
+		progname = argv[0];
+
+	while ((opt = getopt_long(argc, argv, "hmdMo:k:n:", long_opts, NULL)) != -1) {
+		switch (opt) {
+		case 'h':
+			conf_usage(progname);
+			return 0;
+
+		case 'm':
+			merge_mode = true;
+			break;
+
+		case 'd':
+			diff_mode = true;
+			break;
+
+		case 'M':
+			min_mode = true;
+			break;
+
+		case 'o':
+			out_file = optarg;
+			break;
+
+		case 'k':
+			kconfig_file = optarg;
+			break;
+
+		case 'n':
+			new_file = optarg;
+			break;
+
+		default:;
+		}
+	}
+
+	if (optind == argc) {
+		fprintf(stderr, "%s: Missing config file\n", progname);
+		goto err_usage;
+	}
+
+	if (!merge_mode && !diff_mode) {
+		fprintf(stderr, "%s: Operation mode (merge or diff) not specified\n", progname);
+		goto err_usage;
+	}
+
+	if (merge_mode && diff_mode) {
+		fprintf(stderr, "%s: Only one operation mode can be specified\n", progname);
+		goto err_usage;
+	}
+
+	if (diff_mode && !new_file) {
+		fprintf(stderr, "%s: New config file not specified in diff mode\n", progname);
+		goto err_usage;
+	}
+
+	if (!out_file) {
+		fprintf(stderr, "%s: Output file not specified\n", progname);
+		goto err_usage;
+	}
+
+	if (!kconfig_file) {
+		fprintf(stderr, "%s: Kconfig file not specified\n", progname);
+		goto err_usage;
+	}
+
+	conf_parse(kconfig_file);
+
+	if (merge_mode)
+		return do_conf_merge(argc - optind, argv + optind);
+
+	return do_conf_diff(argc - optind, argv + optind);
+
+err_usage:
+	conf_usage(progname);
+
+	return 1;
+}
diff --git a/autobuild/unified/scripts/master/scripts-feeds-support-subdir.patch b/autobuild/unified/scripts/master/patches/0001-scripts-feeds-support-subdir.patch
similarity index 100%
rename from autobuild/unified/scripts/master/scripts-feeds-support-subdir.patch
rename to autobuild/unified/scripts/master/patches/0001-scripts-feeds-support-subdir.patch
diff --git a/autobuild/unified/scripts/master/patches/0002-scripts-kconfig-add-aconf.patch b/autobuild/unified/scripts/master/patches/0002-scripts-kconfig-add-aconf.patch
new file mode 100644
index 0000000..7612f1d
--- /dev/null
+++ b/autobuild/unified/scripts/master/patches/0002-scripts-kconfig-add-aconf.patch
@@ -0,0 +1,73 @@
+--- a/scripts/config/Makefile
++++ b/scripts/config/Makefile
+@@ -78,6 +78,10 @@ $(obj)/qconf-moc.cc: $(src)/qconf.h FORCE | $(obj)/qconf-bin
+ 
+ targets += qconf-moc.cc
+ 
++# aconf: Used for defconfig merging and incremental subtraction
++hostprogs	+= aconf
++aconf-objs	:= aconf.o $(common-objs)
++
+ # check if necessary packages are available, and configure build flags
+ cmd_conf_cfg = $< $(addprefix $(obj)/$*conf-, cflags libs bin); touch $(obj)/$*conf-bin
+ 
+--- a/scripts/config/confdata.c
++++ b/scripts/config/confdata.c
+@@ -223,7 +223,7 @@ static const char *conf_get_rustccfg_name(void)
+ 	return name ? name : "include/generated/rustc_cfg";
+ }
+ 
+-static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
++int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
+ {
+ 	char *p2;
+ 
+@@ -307,7 +307,7 @@ static int add_byte(int c, char **lineptr, size_t slen, size_t *n)
+ 	return 0;
+ }
+ 
+-static ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream)
++ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream)
+ {
+ 	char *line = *lineptr;
+ 	size_t slen = 0;
+@@ -718,7 +718,7 @@ static void __print_symbol(FILE *fp, struct symbol *sym, enum output_n output_n,
+ 	free(escaped);
+ }
+ 
+-static void print_symbol_for_dotconfig(FILE *fp, struct symbol *sym)
++void print_symbol_for_dotconfig(FILE *fp, struct symbol *sym)
+ {
+ 	__print_symbol(fp, sym, OUTPUT_N_AS_UNSET, true);
+ }
+--- a/scripts/config/lkc.h
++++ b/scripts/config/lkc.h
+@@ -43,6 +43,9 @@ extern int recursive_is_error;
+ /* confdata.c */
+ const char *conf_get_configname(void);
+ void set_all_choice_values(struct symbol *csym);
++int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p);
++void print_symbol_for_dotconfig(FILE *fp, struct symbol *sym);
++ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream);
+ 
+ /* confdata.c and expr.c */
+ static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out)
+@@ -114,6 +117,7 @@ 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 symbol *prop_get_symbol(struct property *prop);
++unsigned strhash(const char *s);
+ 
+ static inline tristate sym_get_tristate_value(struct symbol *sym)
+ {
+--- a/scripts/config/symbol.c
++++ b/scripts/config/symbol.c
+@@ -772,7 +772,7 @@ bool sym_is_changeable(struct symbol *sym)
+ 	return sym->visible > sym->rev_dep.tri;
+ }
+ 
+-static unsigned strhash(const char *s)
++unsigned strhash(const char *s)
+ {
+ 	/* fnv32 hash */
+ 	unsigned hash = 2166136261U;