[][openwrt-24][common][image][Add unified autobuild framework]

[Description]
Add unified autobuild framework

[Release-log]
N/A

Change-Id: I6d070071082899f024cd34cae7b8209d46452592
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/9541683
diff --git a/autobuild/unified/.gitignore b/autobuild/unified/.gitignore
new file mode 100644
index 0000000..862a1cc
--- /dev/null
+++ b/autobuild/unified/.gitignore
@@ -0,0 +1,8 @@
+/*
+!/scripts
+!/global
+!/filogic
+
+!/autobuild.sh
+!/rules
+!.gitignore
diff --git a/autobuild/unified/autobuild.sh b/autobuild/unified/autobuild.sh
new file mode 100755
index 0000000..ddfa76c
--- /dev/null
+++ b/autobuild/unified/autobuild.sh
@@ -0,0 +1,354 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Autobuild framework for OpenWrt
+
+# Autobuild script base directory
+ab_root="$(dirname "$(readlink -f "$0")")"
+
+. ${ab_root}/scripts/log.sh
+. ${ab_root}/scripts/list.sh
+. ${ab_root}/scripts/kconfig.sh
+. ${ab_root}/scripts/openwrt_helpers.sh
+. ${ab_root}/scripts/ab-common.sh
+
+# Command line processing
+
+## Parse autobuild branch name
+ab_branch=
+ab_branch_names=
+ab_branch_level=0
+
+if autobuild_branch_name_check "${1}"; then
+	canonicalize_autobuild_branch_name "${1}" ab_branch_names ab_branch
+	shift
+fi
+
+## Stages
+ab_stages="prepare build release"
+do_menuconfig=
+do_help=
+do_list=
+do_clean=
+
+if list_find ab_stages "${1}"; then
+	ab_stages="${1}"
+	shift
+elif test x"${1}" = x"sdk_release"; then
+	ab_stages="prepare sdk_release"
+	shift
+elif test x"${1}" = x"menuconfig"; then
+	ab_stages="prepare menuconfig"
+	do_menuconfig=1
+	shift
+elif test x"${1}" = x"help"; then
+	ab_stages="help"
+	do_help=1
+	shift
+elif test x"${1}" = x"list"; then
+	do_list=1
+	shift
+elif test x"${1}" = x"clean"; then
+	do_clean=1
+	shift
+fi
+
+## Options
+__logged_args=()
+for arg in "$@"; do
+	if expr index ${arg} '=' 2>&1 >/dev/null; then
+		name=${arg%%=*}
+		value=${arg#*=}
+
+		eval ${name}=\"\${value}\"
+		eval ${name}_set=yes
+
+		__logged_args[${#__logged_args[@]}]="${name}=\"${value}\""
+	else
+		eval ${arg}_set=yes
+
+		__logged_args[${#__logged_args[@]}]="${arg}"
+	fi
+done
+
+IFS=$'\n' __sorted_args=($(sort <<< "${__logged_args[*]}")); unset IFS
+IFS=' ' ab_cmdline="${__logged_args[@]}"; unset IFS
+
+# Enable debugging log?
+if test x"${debug_set}" = x"yes"; then
+	enable_log_debug
+fi
+
+# Enable logging to file?
+if test x"${log_file_set}" = x"yes"; then
+	log_file_merge=
+
+	[ x"${log_file_merge_set}" = x"yes" ] && log_file_merge=1
+
+	set_log_file_prefix "${log_file}" ${log_file_merge}
+fi
+
+# OK, do list here if required
+if test x"${do_list}" = x"1"; then
+	list_all_autobuild_branches
+	exit 0
+fi
+
+openwrt_root="$(pwd)"
+
+if ! is_openwrt_build_root "${openwrt_root}"; then
+	log_err "${openwrt_root} is not a OpenWrt root directory."
+	log_err "The autobuild script must be called from within the OpenWrt root directory."
+	exit 1
+fi
+
+# OpenWrt branch (master, 21.02, ...)
+openwrt_branch=$(openwrt_get_branch)
+
+if test -z "${openwrt_branch}"; then
+	log_err "Failed to get OpenWrt's branch"
+	exit 1
+else
+	log_info "OpenWrt's branch is ${openwrt_branch}"
+fi
+
+# Temporary directory for storing configs and intermediate files
+ab_tmp="${openwrt_root}/.ab"
+
+# Default release directory
+ab_bin_release="${openwrt_root}/autobuild_release"
+
+# OK, do clean here if required
+if test x"${do_clean}" = x"1"; then
+	if ! git status >/dev/null; then
+		log_warn "The clean stage can only be applied if the OpenWrt source is managed by Git."
+		exit 1
+	fi
+
+	exec_log "git -C \"${openwrt_root}\" checkout ."
+	exec_log "git -C \"${openwrt_root}\" clean -f -d -e autobuild"
+	exec_log "rm -rf \"${openwrt_root}/feeds\""
+	exec_log "rm -rf \"${openwrt_root}/package/feeds\""
+	exec_log "rm -rf \"${openwrt_root}/.ab\""
+
+	exit 0
+fi
+
+# Check if we need prepare for build stage
+if list_find ab_stages build; then
+	if test \( ! -f "${ab_tmp}/branch_name" \) -o -f "${ab_tmp}/.stamp.menuconfig"; then
+		list_add_before_unique ab_stages build prepare
+	fi
+fi
+
+# Check for prepare stage
+if list_find ab_stages prepare; then
+	if test -f "${ab_tmp}/branch_name"; then
+		# If prepare stage has been done before, check whether clean is required
+
+		if test -z "${do_menuconfig}" -a -f "${ab_tmp}/.stamp.menuconfig"; then
+			log_warn "This OpenWrt source code has already been prepared for menuconfig."
+			log_warn "Please call \`${0} clean' first."
+			exit 1
+		fi
+
+		if test -n "${do_menuconfig}" -a ! -f "${ab_tmp}/.stamp.menuconfig"; then
+			log_warn "This OpenWrt source code has already been prepared, but not for menuconfig."
+			log_warn "Please call \`${0} clean' first."
+			exit 1
+		fi
+
+		last_ab_branch=$(cat "${ab_tmp}/branch_name")
+
+		if test -n "${ab_branch}"; then
+			if test x"${ab_branch}" != x"${last_ab_branch}"; then
+				log_warn "Autobuild branch name has changed."
+				log_warn "Please call \`${0} clean' first."
+				exit 1
+			fi
+
+			if test -z "${do_menuconfig}"; then
+				last_ab_cmdline=$(cat "${ab_tmp}/cmdline")
+
+				if test x"${ab_cmdline}" != x"${last_ab_cmdline}"; then
+					log_warn "Autobuild configuration has changed."
+					log_warn "Please call \`${0} clean' first."
+					exit 1
+				fi
+			fi
+
+			# Configuration unchanged
+		else
+			# Read previous configuration
+			canonicalize_autobuild_branch_name "${last_ab_branch}" ab_branch_names ab_branch
+		fi
+
+		# prepare stage is not needed
+		list_del ab_stages prepare
+	else
+		if test -z "${ab_branch}" -a x"${do_help}" != x"1"; then
+			log_err "Autobuild branch name is invalid or not specified."
+			print_text   "Quick start:"
+			print_text   "- To show detailed help:"
+			log_info_raw "  ${0} help"
+			print_text   "- To start full build for a branch (<branch-name> example: mtxxxx-mtxxxx-xxx):"
+			log_info_raw "  ${0} <branch-name>"
+			print_text   "- To continue current build:"
+			log_info_raw "  ${0} build"
+			print_text   "- To clean everything under OpenWrt source tree:"
+			log_info_raw "  ${0} clean"
+			print_text   "- To start menuconfig and update defconfig for specified branch:"
+			log_info_raw "  ${0} <branch-name> menuconfig"
+			print_text   "- To list all available branch names:"
+			log_info_raw "  ${0} <branch-name> list"
+			exit 1
+		fi
+	fi
+else
+	if test -z "${ab_branch}" -a -f "${ab_tmp}/branch_name"; then
+		last_ab_branch=$(cat "${ab_tmp}/branch_name")
+
+		# Read previous configuration
+		canonicalize_autobuild_branch_name "${last_ab_branch}" ab_branch_names ab_branch
+	fi
+fi
+
+## Fill branch names
+ab_branch_level=${#ab_branch_names[@]}
+ab_branch_platform=${ab_branch_names[0]}
+ab_branch_wifi=${ab_branch_names[1]}
+ab_branch_sku=${ab_branch_names[2]}
+ab_branch_variant=${ab_branch_names[3]}
+
+## Set and print branch configuration
+[ -n "${ab_branch}" ] && log_info "Autobuild branch: ${ab_branch}"
+
+if test -n "${ab_branch_platform}"; then
+	ab_branch_level=1
+	ab_platform_dir=${ab_root}/${ab_branch_platform}
+	print_conf "Platform" "${ab_branch_platform}"
+fi
+
+if test -n "${ab_branch_wifi}"; then
+	ab_branch_level=2
+	ab_wifi_dir=${ab_platform_dir}/${ab_branch_wifi}
+	print_conf "WiFi" "${ab_branch_wifi}"
+fi
+
+if test -n "${ab_branch_sku}"; then
+	ab_branch_level=3
+	ab_sku_dir=${ab_wifi_dir}/${ab_branch_sku}
+	print_conf "SKU" "${ab_branch_sku}"
+fi
+
+if test -n "${ab_branch_variant}"; then
+	ab_branch_level=4
+	ab_variant_dir=${ab_sku_dir}/${ab_branch_variant}
+	print_conf "Variant" "${ab_branch_variant}"
+fi
+
+# Setup global settings
+
+## Set new binary release directory
+if test x"${release_dir_set}" = x"yes"; then
+	ab_bin_release="${openwrt_root}/${release_dir}"
+fi
+
+ab_bin_release="${ab_bin_release}/${ab_branch}"
+
+## Set global directories
+ab_global=${ab_root}/global
+
+# Setup help text
+help_add_line ""
+help_add_line "Autobuild script for OpenWrt"
+help_add_line ""
+help_add_line "Usage: autobuild.sh [branch] [stage] [options...]"
+help_add_line ""
+help_add_line "Branch: <platform>[-<wifi>[-<sku>[-<variant>]]]"
+help_add_line "  Branch is only required for prepare. It can be omitted for build/release"
+help_add_line "  after a successful prepare."
+help_add_line ""
+help_add_line "Stages:"
+help_add_line "  (If stage is not specified, default will be \"prepare/build/release\")"
+help_add_line "  prepare     - Prepare the OpenWrt source code."
+help_add_line "                Do patching, copying/deleting files."
+help_add_line "                Once prepared, clean must be done before another prepare."
+help_add_line "  build       - Do actual build."
+help_add_line "  release     - Collect built binary files only."
+help_add_line "  sdk_release - Do source code release. MediaTek internal use only."
+help_add_line "  menuconfig  - Do menuconfig for specified branch. defconfig of this branch"
+help_add_line "                will be updated."
+help_add_line "  clean       - Clean all modified/untraced files/directories from OpenWrt"
+help_add_line "                source code."
+help_add_line "  list        - List all available branch names."
+help_add_line "  help        - Show this help."
+help_add_line ""
+help_add_line "Options:"
+help_add_line "  The options can be <key>=<value>, or just simple <key>."
+help_add_line "  log_file=<file> - Enable log output to file."
+help_add_line "  log_file_merge - Log stdout and stderr to one file."
+help_add_line "  debug - Enable debug log output."
+help_add_line "  release_dir=<dir> - Override default release directory."
+help_add_line "    Default directory is 'autobuild_release' under OpenWrt's source directory."
+
+# Include branch rules (the rule is child level overriding parent level)
+. "${ab_root}/rules"
+[ -f "${ab_global}/${openwrt_branch}/rules" ] && . "${ab_global}/${openwrt_branch}/rules"
+[ -n "${ab_platform_dir}" ] && . "${ab_platform_dir}/rules"
+[ -n "${ab_wifi_dir}" ] && . "${ab_wifi_dir}/rules"
+[ -n "${ab_sku_dir}" ] && . "${ab_sku_dir}/rules"
+[ -n "${ab_variant_dir}" ] && . "${ab_variant_dir}/rules"
+
+# Show help?
+if test x"${do_help}" = x"1"; then
+	help_print
+	exit 0
+fi
+
+# Run stages
+log_dbg "All stages: ${ab_stages}"
+
+for stage in ${ab_stages}; do
+	substages=$(get_substages "${stage}")
+
+	prompt_stage "Current stage: \"${stage}\""
+	log_dbg "Substages of ${stage}: ${substages}"
+
+	for substage in ${substages}; do
+		hooks=$(get_hooks "${substage}")
+
+		prompt_stage "Current substage: \"${substage}\""
+
+		[ -z "${hooks}" ] && hooks="${substage}"
+		clean_hooks hooks
+
+		if test -z "${hooks}"; then
+			log_info "Nothing to do with substage ${substage}"
+			continue
+		fi
+
+		log_dbg "Hooks of substage \"${substage}\": ${hooks}"
+
+		for hook in ${hooks}; do
+			prompt_stage "Executing hook \"${hook}\""
+
+			eval "${hook}"
+
+			ret=$?
+
+			if test ${ret} != 0; then
+				log_err "${stage}/${substage}/${hook} exited with error code ${ret}"
+				exit 1
+			fi
+		done
+	done
+done
+
+# All done
+if list_find ab_stages build; then
+	log_info "Autobuild finished"
+fi
+
+exit 0
diff --git a/autobuild/unified/global/kasan.sh b/autobuild/unified/global/kasan.sh
new file mode 100755
index 0000000..f90baf7
--- /dev/null
+++ b/autobuild/unified/global/kasan.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Rules for enabling KASAN
+
+enable_kasan_openwrt() {
+	openwrt_config_enable CONFIG_KERNEL_KASAN
+	openwrt_config_enable CONFIG_KERNEL_KASAN_GENERIC
+	openwrt_config_enable CONFIG_KERNEL_KALLSYMS
+	openwrt_config_enable CONFIG_KERNEL_KASAN_OUTLINE
+	openwrt_config_disable CONFIG_KERNEL_KASAN_INLINE
+	openwrt_config_enable CONFIG_KERNEL_SLUB_DEBUG
+	openwrt_config_enable CONFIG_KERNEL_FRAME_WARN 4096
+}
+
+enable_kasan_kernel() {
+	kernel_config_enable CONFIG_DEBUG_KMEMLEAK
+	kernel_config_enable CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN
+	kernel_config_disable CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF
+	kernel_config_enable CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE 16000
+	kernel_config_enable CONFIG_DEBUG_KMEMLEAK_TEST m
+	# Still need to enable kernel configs for 21.02
+	kernel_config_enable CONFIG_KALLSYMS
+	kernel_config_enable CONFIG_KASAN
+	kernel_config_enable CONFIG_KASAN_GENERIC
+	kernel_config_disable CONFIG_KASAN_INLINE
+	kernel_config_enable CONFIG_KASAN_OUTLINE
+	kernel_config_enable CONFIG_KASAN_SHADOW_OFFSET 0xdfffffd000000000
+	kernel_config_disable CONFIG_TEST_KASAN
+	kernel_config_enable CONFIG_SLUB_DEBUG
+	kernel_config_enable CONFIG_FRAME_WARN 4096
+}
+
+if test x"${kasan_set}" == x"yes"; then
+	list_add_before $(hooks autobuild_prepare) make_defconfig enable_kasan_openwrt
+	list_add_after $(hooks autobuild_prepare) variant_change_kernel_config enable_kasan_kernel
+fi
+
+help_add_line "  kasan - Enable KASAN."
diff --git a/autobuild/unified/global/master/patches-base/0001-kernel-build-mk-do-not-strip-kernel-debug-modules.patch b/autobuild/unified/global/master/patches-base/0001-kernel-build-mk-do-not-strip-kernel-debug-modules.patch
new file mode 100644
index 0000000..62e47b4
--- /dev/null
+++ b/autobuild/unified/global/master/patches-base/0001-kernel-build-mk-do-not-strip-kernel-debug-modules.patch
@@ -0,0 +1,17 @@
+From: Weijie Gao <weijie.gao@mediatek.com>
+Subject: [PATCH] kernel-build.mk: do not strip kernel debug modules
+
+Only the full ELF file is usable for TRACE32 analysis
+
+Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
+
+--- a/include/kernel-build.mk
++++ b/include/kernel-build.mk
+@@ -65,7 +65,6 @@ ifdef CONFIG_COLLECT_KERNEL_DEBUG
+ 	-$(CP) \
+ 		$(STAGING_DIR_ROOT)/lib/modules/$(LINUX_VERSION)/*.ko \
+ 		$(KERNEL_BUILD_DIR)/debug/modules/
+-	$(FIND) $(KERNEL_BUILD_DIR)/debug -type f | $(XARGS) $(KERNEL_CROSS)strip --only-keep-debug
+ 	$(TAR) c -C $(KERNEL_BUILD_DIR) debug \
+ 		$(if $(SOURCE_DATE_EPOCH),--mtime="@$(SOURCE_DATE_EPOCH)") \
+ 		| zstd -T0 -f -o $(BIN_DIR)/kernel-debug.tar.zst
diff --git a/autobuild/unified/rules b/autobuild/unified/rules
new file mode 100755
index 0000000..449fb3d
--- /dev/null
+++ b/autobuild/unified/rules
@@ -0,0 +1,588 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Top rules
+
+# Declare substages of every stage
+sub_stage_prepare="mk_ab_tmp pre_prepare apply_feed_script_patch modify_feeds_conf update_feeds \
+		   mtk_feed_prepare install_feeds autobuild_prepare post_prepare"
+sub_stage_build="pre_build do_build post_build"
+sub_stage_release="pre_release do_release post_release"
+sub_stage_sdk_release="pre_sdk_release do_sdk_release post_sdk_release"
+sub_stage_menuconfig="do_menuconfig_update"
+
+# Declare hooks of every substage
+hooks_pre_prepare=
+hooks_mtk_feed_prepare="remove_files_by_mtk_feed_list copy_mtk_feed_files apply_mtk_feed_base_patches \
+			apply_mtk_feed_feed_patches"
+hooks_autobuild_prepare="remove_files_by_global_list copy_global_files apply_global_patches \
+			 remove_files_by_platform_list copy_platform_files apply_platform_patches \
+			 remove_files_by_wifi_list copy_wifi_files apply_wifi_patches \
+			 remove_files_by_sku_list copy_sku_files apply_sku_patches \
+			 remove_files_by_variant_list copy_variant_files apply_variant_patches \
+			 prepare_wifi_driver prepare_openwrt_config platform_change_openwrt_config \
+			 wifi_change_openwrt_config sku_change_openwrt_config variant_change_openwrt_config \
+			 enable_openwrt_collect_debug_symbols make_defconfig update_target_info \
+			 platform_change_kernel_config wifi_change_kernel_config sku_change_kernel_config \
+			 variant_change_kernel_config enable_kernel_proc_config_gz"
+hooks_post_prepare="prepare_stamp"
+
+hooks_pre_build=
+hooks_do_build="download_openwrt_packages build_openwrt"
+hooks_post_build=
+
+hooks_pre_release="update_target_info mk_ab_bin_release"
+hooks_do_release="collect_openwrt_images collect_openwrt_configs collect_kernel_debug_symbols \
+		      collect_userspace_debug_symbols collect_feeds_buildinfo"
+hooks_post_release=
+
+hooks_pre_sdk_release=
+hooks_do_sdk_release="release_openwrt_sdk"
+hooks_post_sdk_release=
+
+# Global information
+build_time=
+internal_build=
+mtk_feed_path=
+kernel_ver=
+target_name=
+subtarget_name=
+
+openwrt_bin_dir=
+openwrt_config_file=
+kernel_config_file=
+
+# Generic function for modifying openwrt config, to be compliant with menuconfig
+# $1:	Config name
+openwrt_config_disable() {
+	if test -z "${do_menuconfig}"; then
+		if kconfig_enabled "${openwrt_config_file}" "${1}"; then
+			kconfig_disable "${openwrt_config_file}" "${1}"
+		fi
+	fi
+}
+
+# $1:	Config name
+# $2:	Value to be set for config (y if not specified)
+openwrt_config_enable() {
+	if test -z "${do_menuconfig}"; then
+		if ! kconfig_enabled "${openwrt_config_file}" "${1}"; then
+			kconfig_enable "${openwrt_config_file}" "${1}" "${2}"
+		fi
+	fi
+}
+
+# $1:	Config name
+openwrt_config_enabled() {
+	kconfig_enabled "${openwrt_config_file}" "${1}"
+}
+
+# Generic function for modifying target's kernel config
+# $1:	Config name
+kernel_config_disable() {
+	if kconfig_enabled "${kernel_config_file}" "${1}"; then
+		kconfig_disable "${kernel_config_file}" "${1}"
+	fi
+}
+
+# $1:	Config name
+# $2:	Value to be set for config (y if not specified)
+kernel_config_enable() {
+	if ! kconfig_enabled "${kernel_config_file}" "${1}"; then
+		kconfig_enable "${kernel_config_file}" "${1}" "${2}"
+	fi
+}
+
+# $1:	Config name
+kernel_config_enabled() {
+	kconfig_enabled "${kernel_config_file}" "${1}"
+}
+
+# Create the ${ab_tmp} directory
+mk_ab_tmp() {
+	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_feeds_conf() {
+	local rev_file_list=
+	local feed_rev=
+	local feed_url="https://git01.mediatek.com/openwrt/feeds/mtk-openwrt-feeds"
+
+	# Backup original feeds
+	exec_log "cp -f \"${openwrt_root}/feeds.conf.default\" \"${ab_tmp}/\""
+
+	# Modify feeds
+	openwrt_feeds_replace_url packages https://gerrit.mediatek.inc/openwrt/feeds/packages
+	openwrt_feeds_replace_url luci https://gerrit.mediatek.inc/openwrt/feeds/luci
+	openwrt_feeds_replace_url routing https://gerrit.mediatek.inc/openwrt/feeds/routing
+	openwrt_feeds_disable telephony
+	openwrt_feeds_change_src_git_type packages 0
+	openwrt_feeds_change_src_git_type luci 0
+	openwrt_feeds_change_src_git_type routing 0
+
+	# Add mtk-openwrt-feeds
+	[ -n "${ab_variant_dir}" ] && list_append rev_file_list "${ab_variant_dir}/feed_revision"
+	[ -n "${ab_sku_dir}" ] && list_append rev_file_list "${ab_sku_dir}/feed_revision"
+	[ -n "${ab_wifi_dir}" ] && list_append rev_file_list "${ab_wifi_dir}/feed_revision"
+	[ -n "${ab_platform_dir}" ] && list_append rev_file_list "${ab_platform_dir}/feed_revision"
+	list_append rev_file_list "${ab_root}/feed_revision"
+
+	for rev_file in ${rev_file_list}; do
+		if test -f "${rev_file}"; then
+			feed_rev=$(cat "${rev_file}")
+			break;
+		fi
+	done
+
+	[ -n "${feed_rev}" ] && feed_rev="^${feed_rev}"
+
+	if test -d "${openwrt_root}/../mtk-openwrt-feeds"; then
+		feed_url="${openwrt_root}/../mtk-openwrt-feeds"
+		internal_build=1
+
+		log_dbg "Internal repo build mode"
+	fi
+
+	if test -n "${internal_build}" -a -z "${feed_rev}"; then
+		openwrt_feeds_add mtk_openwrt_feed src-link "${feed_url}" --subdir=feed
+	else
+		openwrt_feeds_add mtk_openwrt_feed src-git "${feed_url}${feed_rev}" --subdir=feed
+	fi
+}
+
+update_feeds() {
+	exec_log "${openwrt_root}/scripts/feeds update -a"
+
+	mtk_feed_path=${openwrt_root}/feeds/mtk_openwrt_feed
+}
+
+remove_files_by_mtk_feed_list() {
+	remove_files_from_list "${mtk_feed_path}/common/remove_list.txt"
+	remove_files_from_list "${mtk_feed_path}/${openwrt_branch}/remove_list.txt"
+}
+
+copy_mtk_feed_files() {
+	copy_files "${mtk_feed_path}/common/files"
+	copy_files "${mtk_feed_path}/tools" tools
+	copy_files "${mtk_feed_path}/${openwrt_branch}/files"
+}
+
+apply_mtk_feed_base_patches() {
+	apply_patches "${mtk_feed_path}/${openwrt_branch}/patches-base"
+}
+
+apply_mtk_feed_feed_patches() {
+	apply_patches "${mtk_feed_path}/${openwrt_branch}/patches-feeds"
+}
+
+install_feeds() {
+	exec_log "${openwrt_root}/scripts/feeds install -a"
+}
+
+remove_files_by_global_list() {
+	remove_files_from_list "${ab_global}/common/remove_list.txt"
+	remove_files_from_list "${ab_global}/${openwrt_branch}/remove_list.txt"
+}
+
+copy_global_files() {
+	copy_files "${ab_global}/common/files"
+	copy_files "${ab_global}/${openwrt_branch}/files"
+}
+
+apply_global_patches() {
+	apply_patches "${ab_global}/${openwrt_branch}/patches-base" || return 1
+	apply_patches "${ab_global}/${openwrt_branch}/patches-feeds" || return 1
+	apply_patches "${ab_global}/${openwrt_branch}/patches" || return 1
+}
+
+remove_files_by_platform_list() {
+	remove_files_from_list "${ab_platform_dir}/remove_list.txt"
+	remove_files_from_list "${ab_platform_dir}/${openwrt_branch}/remove_list.txt"
+}
+
+copy_platform_files() {
+	copy_files "${ab_platform_dir}/files"
+	copy_files "${ab_platform_dir}/${openwrt_branch}/files"
+}
+
+apply_platform_patches() {
+	apply_patches "${ab_platform_dir}/${openwrt_branch}/patches-base" || return 1
+	apply_patches "${ab_platform_dir}/${openwrt_branch}/patches-feeds" || return 1
+	apply_patches "${ab_platform_dir}/${openwrt_branch}/patches" || return 1
+}
+
+remove_files_by_wifi_list() {
+	if test -n "${ab_wifi_dir}"; then
+		remove_files_from_list "${ab_wifi_dir}/remove_list.txt"
+		remove_files_from_list "${ab_wifi_dir}/${openwrt_branch}/remove_list.txt"
+	fi
+}
+
+copy_wifi_files() {
+	if test -n "${ab_wifi_dir}"; then
+		copy_files "${ab_wifi_dir}/files"
+		copy_files "${ab_wifi_dir}/${openwrt_branch}/files"
+	fi
+}
+
+apply_wifi_patches() {
+	if test -n "${ab_wifi_dir}"; then
+		apply_patches "${ab_wifi_dir}/${openwrt_branch}/patches-base" || return 1
+		apply_patches "${ab_wifi_dir}/${openwrt_branch}/patches-feeds" || return 1
+		apply_patches "${ab_wifi_dir}/${openwrt_branch}/patches" || return 1
+	fi
+}
+
+remove_files_by_sku_list() {
+	if test -n "${ab_sku_dir}"; then
+		remove_files_from_list "${ab_sku_dir}/remove_list.txt"
+		remove_files_from_list "${ab_sku_dir}/${openwrt_branch}/remove_list.txt"
+	fi
+}
+
+copy_sku_files() {
+	if test -n "${ab_sku_dir}"; then
+		copy_files "${ab_sku_dir}/files"
+		copy_files "${ab_sku_dir}/${openwrt_branch}/files"
+	fi
+}
+
+apply_sku_patches() {
+	if test -n "${ab_sku_dir}"; then
+		apply_patches "${ab_sku_dir}/${openwrt_branch}/patches-base" || return 1
+		apply_patches "${ab_sku_dir}/${openwrt_branch}/patches-feeds" || return 1
+		apply_patches "${ab_sku_dir}/${openwrt_branch}/patches" || return 1
+	fi
+}
+
+remove_files_by_variant_list() {
+	if test -n "${ab_variant_dir}"; then
+		remove_files_from_list "${ab_sku_dir}/remove_list.txt"
+		remove_files_from_list "${ab_sku_dir}/${openwrt_branch}/remove_list.txt"
+	fi
+}
+
+copy_variant_files() {
+	if test -n "${ab_variant_dir}"; then
+		copy_files "${ab_variant_dir}/files"
+		copy_files "${ab_variant_dir}/${openwrt_branch}/files"
+	fi
+}
+
+apply_variant_patches() {
+	if test -n "${ab_variant_dir}"; then
+		apply_patches "${ab_variant_dir}/${openwrt_branch}/patches-base" || return 1
+		apply_patches "${ab_variant_dir}/${openwrt_branch}/patches-feeds" || return 1
+		apply_patches "${ab_variant_dir}/${openwrt_branch}/patches" || return 1
+	fi
+}
+
+# prepare_wifi_driver() { } Implemented by wifi inclusion
+
+prepare_openwrt_config() {
+	local kconfig_expr=
+	local kconfig_files="\"${ab_platform_dir}/${openwrt_branch}/defconfig\""
+
+	openwrt_config_file="${openwrt_root}/.config"
+
+	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_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\""
+}
+
+# {platform,wifi,sku,variant}_change_openwrt_config() { } Implemented by platform sub levels
+
+enable_openwrt_collect_debug_symbols() {
+	openwrt_config_enable CONFIG_COLLECT_KERNEL_DEBUG
+	openwrt_config_enable CONFIG_DEBUG
+	openwrt_config_disable CONFIG_KERNEL_DEBUG_INFO_REDUCED
+}
+
+make_defconfig() {
+	if test -f "${ab_tmp}/.config.prev" -a -f "${openwrt_root}/.config"; then
+		if ! cmp -s "${ab_tmp}/.config.prev" "${openwrt_root}/.config"; then
+			exec_log "make -C \"${openwrt_root}\" defconfig"
+		fi
+	fi
+}
+
+__target_info_updated=
+
+update_target_info() {
+	[ -n "${__target_info_updated}" ] && return 0
+
+	target_name=$(openwrt_get_target_name)
+	subtarget_name=$(openwrt_get_subtarget_name)
+
+	if test -z "${target_name}"; then
+		log_err "Failed to get OpenWrt's target name"
+		return 1
+	fi
+
+	log_info "Target name: ${target_name}"
+	[ -n "${subtarget_name}" ] && log_info "Subtarget name: ${subtarget_name}"
+
+	openwrt_bin_dir=$(openwrt_get_bin_dir)
+
+	if test -z "${openwrt_bin_dir}"; then
+		log_err "Failed to get OpenWrt's bin directory"
+		return 1
+	fi
+
+	log_dbg "Target bin dir: ${openwrt_bin_dir}"
+
+	kernel_ver=$(openwrt_get_target_kernel_version ${target_name})
+
+	if test -z "${kernel_ver}"; then
+		log_err "Failed to get OpenWrt's target kernel version"
+		return 1
+	else
+		log_info "Target kernel version: ${kernel_ver}"
+	fi
+
+	if test -n ${subtarget_name}; then
+		if test -f "${openwrt_root}/target/linux/${target_name}/${subtarget_name}/config-${kernel_ver}"; then
+			kernel_config_file="${openwrt_root}/target/linux/${target_name}/${subtarget_name}/config-${kernel_ver}"
+		fi
+	fi
+
+	if test -z ${kernel_config_file}; then
+		if test -f "${openwrt_root}/target/linux/${target_name}/config-${kernel_ver}"; then
+			kernel_config_file="${openwrt_root}/target/linux/${target_name}/config-${kernel_ver}"
+		fi
+	fi
+
+	if test -z ${kernel_config_file}; then
+		log_err "Unable to find target's kernel config file"
+		return 1
+	fi
+
+	log_dbg "Target kernel config file: ${kernel_config_file}"
+
+	__target_info_updated=1
+}
+
+# {platform,wifi,sku,variant}_change_kernel_config() { } Implemented by platform sub levels
+
+enable_kernel_proc_config_gz() {
+	kernel_config_enable CONFIG_IKCONFIG
+	kernel_config_enable CONFIG_IKCONFIG_PROC
+}
+
+prepare_stamp() {
+	if test -n "${do_menuconfig}"; then
+		touch "${ab_tmp}/.stamp.menuconfig"
+	else
+		rm -f "${ab_tmp}/.stamp.menuconfig"
+	fi
+
+	echo -n "${ab_branch}" > "${ab_tmp}/branch_name"
+	echo -n "${ab_cmdline}" > "${ab_tmp}/cmdline"
+}
+
+download_openwrt_packages() {
+	if test x"${internal_build}" = x"1"; then
+		if ! [ -d "${openwrt_root}/dl" -o -L "${openwrt_root}/dl" ]; then
+			exec_log "ln -sf ../dl \"${openwrt_root}/dl\""
+		fi
+	fi
+
+	exec_log "make -C \"${openwrt_root}\" V=1 -j\$((\$(nproc) + 1)) download"
+}
+
+build_openwrt() {
+	local ret=
+	local verbose=1
+
+	if test x"${debug_set}" = x"yes"; then
+		verbose=s
+	fi
+
+	build_time=$(date +%Y%m%d%H%M%S)
+
+	exec_log "make -C \"${openwrt_root}\" V=${verbose} -j\$((\$(nproc) + 1))"
+
+	ret=$?
+
+	if test ${ret} != 0; then
+		log_warn "Build failed with error code ${ret}."
+		log_warn "Restart single-threaded building for debugging purpose."
+
+		exec_log "make -C \"${openwrt_root}\" V=s -j1"
+
+		ret=$?
+
+		if test ${ret} != 0; then
+			log_err "Debug build failed with error code ${ret}."
+			return 1
+		fi
+	fi
+
+	log_info "OpenWrt built successfully"
+}
+
+# Create the ${ab_bin_release} directory
+mk_ab_bin_release() {
+	exec_log "mkdir -p \"${ab_bin_release}\""
+}
+
+collect_openwrt_images() {
+	local file_count=0
+	local files=
+
+	if [ -z "${build_time}" ]; then
+		build_time=$(date +%Y%m%d%H%M%S)
+	fi
+
+	files=$(find "${openwrt_bin_dir}" -maxdepth 1 -name '*.bin' -o -name "*.img" -o -name '*.itb' -o -name '*.gz')
+
+	for file in ${files}; do
+		local file_no_ext=${file%.*}
+		local file_name=${file_no_ext##*/}
+		local file_ext=${file##*.}
+
+		exec_log "cp -rf \"${file}\" \"${ab_bin_release}/${file_name}-${build_time}.${file_ext}\""
+		((file_count++))
+	done
+
+	log_info "Total ${file_count} image files copied."
+}
+
+collect_openwrt_configs() {
+	local linux_dir=$(openwrt_get_target_kernel_linux_build_dir ${target_name})
+
+	exec_log "cp -f \"${openwrt_root}/.config\" \"${ab_bin_release}/openwrt.config\""
+
+	if test -z "${linux_dir}"; then
+		log_warn "Failed to get OpenWrt's linux kernel build directory"
+	else
+		local kernel_config_data="${linux_dir}/kernel/config_data"
+
+		if [ -f "${kernel_config_data}" ]; then
+			exec_log "cp -f \"${kernel_config_data}\" \"${ab_bin_release}/kernel.config\""
+		fi
+	fi
+}
+
+collect_kernel_debug_symbols() {
+	if [ -f "${openwrt_bin_dir}/kernel-debug.tar.zst" ]; then
+		exec_log "cp -f \"${openwrt_bin_dir}/kernel-debug.tar.zst\" \"${ab_bin_release}/\""
+	fi
+}
+
+collect_userspace_debug_symbols() {
+	local staging_dir_root=$(openwrt_get_staging_dir_root)
+
+	log_dbg "Staging dir root: ${staging_dir_root}"
+
+	if test -d "${staging_dir_root}"; then
+		staging_dir_root_prefix=$(dirname "${staging_dir_root}")
+		staging_dir_root_name=$(basename "${staging_dir_root}")
+
+		exec_log "tar -jcf \"${ab_bin_release}/rootfs-debug.tar.bz2\" -C \"${staging_dir_root_prefix}\" \"${staging_dir_root_name}\""
+	fi
+}
+
+collect_feeds_buildinfo() {
+	if [ -f "${openwrt_bin_dir}/feeds.buildinfo" ]; then
+		exec_log "cp -f \"${openwrt_bin_dir}/feeds.buildinfo\" \"${ab_bin_release}/\""
+	fi
+}
+
+do_menuconfig_update() {
+	local kconfig_expr=
+	local kconfig_files=
+	local final_file=
+
+	exec_log "make -C \"${openwrt_root}\" menuconfig"
+
+	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
+		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
+}
diff --git a/autobuild/unified/scripts/21.02/scripts-feeds-support-subdir.patch b/autobuild/unified/scripts/21.02/scripts-feeds-support-subdir.patch
new file mode 100644
index 0000000..af727c2
--- /dev/null
+++ b/autobuild/unified/scripts/21.02/scripts-feeds-support-subdir.patch
@@ -0,0 +1,72 @@
+--- a/scripts/feeds
++++ b/scripts/feeds
+@@ -120,16 +120,22 @@ sub update_location($$)
+ 	return 0;
+ }
+ 
+-sub update_index($)
++sub update_index($$)
+ {
+ 	my $name = shift;
++	my $subdir = shift;
++	my $real_subdir = "";
+ 
+ 	-d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
+ 	-d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
+ 
++	if (length( $subdir || '' )) {
++		$real_subdir = "/$subdir";
++	}
++
+ 	system("$mk -s prepare-mk OPENWRT_BUILD= TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+-	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"packageinfo\" SCAN_DIR=\"feeds/$name\" SCAN_NAME=\"package\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+-	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"targetinfo\" SCAN_DIR=\"feeds/$name\" SCAN_NAME=\"target\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" SCAN_MAKEOPTS=\"TARGET_BUILD=1\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
++	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"packageinfo\" SCAN_DIR=\"feeds/$name$real_subdir\" SCAN_NAME=\"package\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
++	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"targetinfo\" SCAN_DIR=\"feeds/$name$real_subdir\" SCAN_NAME=\"target\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" SCAN_MAKEOPTS=\"TARGET_BUILD=1\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+ 	system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
+ 	system("ln -sf $name.tmp/.targetinfo ./feeds/$name.targetindex");
+ 
+@@ -756,11 +762,12 @@ sub uninstall {
+ 	return 0;
+ }
+ 
+-sub update_feed($$$$$)
++sub update_feed($$$$$$)
+ {
+ 	my $type=shift;
+ 	my $name=shift;
+ 	my $src=shift;
++	my $subdir=shift;
+ 	my $perform_update=shift;
+ 	my $force_update=shift;
+ 	my $force_relocate=update_location( $name, "@$src" );
+@@ -794,7 +801,7 @@ sub update_feed($$$$$)
+ 		};
+ 	};
+ 	warn "Create index file './feeds/$name.index' \n";
+-	update_index($name) == 0 or do {
++	update_index($name, $subdir) == 0 or do {
+ 		warn "failed.\n";
+ 		return 1;
+ 	};
+@@ -830,16 +837,18 @@ sub update {
+ 	if ( ($#ARGV == -1) or $opts{a}) {
+ 		foreach my $feed (@feeds) {
+ 			my ($type, $name, $src) = @$feed;
+-			update_feed($type, $name, $src, $perform_update, $opts{f}) == 0 or $failed=1;
++			my $subdir = $feed->[3]{subdir};
++			update_feed($type, $name, $src, $subdir, $perform_update, $opts{f}) == 0 or $failed=1;
+ 		}
+ 	} else {
+ 		while ($feed_name = shift @ARGV) {
+ 			foreach my $feed (@feeds) {
+ 				my ($type, $name, $src) = @$feed;
++				my $subdir = $feed->[3]{subdir};
+ 				if($feed_name ne $name) {
+ 					next;
+ 				}
+-				update_feed($type, $name, $src, $perform_update, $opts{f}) == 0 or $failed=1;
++				update_feed($type, $name, $src, $subdir, $perform_update, $opts{f}) == 0 or $failed=1;
+ 			}
+ 		}
+ 	}
diff --git a/autobuild/unified/scripts/ab-common.sh b/autobuild/unified/scripts/ab-common.sh
new file mode 100755
index 0000000..b17910e
--- /dev/null
+++ b/autobuild/unified/scripts/ab-common.sh
@@ -0,0 +1,247 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Helpers for Autobuild (Common part)
+
+__help_text=()
+__use_quilt=
+
+if which quilt > /dev/null; then
+	__quilt_ver=$(quilt --version 2>/dev/null)
+	[ -n "${__quilt_ver}" ] && __use_quilt=1
+fi
+
+# Add help text line
+# $1:	Text line
+help_add_line() {
+	__help_text[${#__help_text[@]}]="${1}"
+}
+
+# Print help text
+help_print() {
+	for i in "${!__help_text[@]}"; do
+		printf "%s\n" "${__help_text[$i]}"
+	done
+}
+
+# Check whether a string is a valid autobuild branch name
+# $1:	Branch name
+autobuild_branch_name_check() {
+	[ -z "${1}" ] && return 1
+
+	local names=$(echo "${1}" | sed 's/-/ /g')
+	local path=
+
+	for field in ${names}; do
+		path="${path}${field}/"
+		if ! test -f "${ab_root}/${path}rules"; then
+			return 1
+		fi
+	done
+
+	return 0
+}
+
+# Canonicalize an autobuild branch name
+# $1:	Branch name
+# $2:	Name of a array to store the each branch level name
+# $3:	Canonicalized branch name
+canonicalize_autobuild_branch_name() {
+	local names=$(echo "${1}" | sed 's/-/ /g')
+	local canonical_name=
+	local name_arr=
+	local i=
+
+	eval "name_arr=(${names})"
+
+	local n=${#name_arr[@]}
+
+	[ ${n} -gt 4 ] && n=4
+
+	[ ${n} -eq 0 ] && return 1
+
+	for i in $(seq 0 $((n-1))); do
+		eval "${2}[$i]=\"${name_arr[$i]}\""
+		canonical_name="${canonical_name}${name_arr[$i]}-"
+	done
+
+	eval "${3}=${canonical_name%-*}"
+
+	return 0
+}
+
+# Clean undefined hooks
+# $1:	Hook name
+clean_hooks() {
+	local tmp_hooks=
+	local new_hooks=
+	local hook=
+
+	eval "tmp_hooks=\"\$${1}\""
+
+	for hook in ${tmp_hooks}; do
+		if type ${hook} >/dev/null 2>&1; then
+			new_hooks="${new_hooks} ${hook}"
+		fi
+	done
+
+	eval "${1}=\"\${new_hooks}\""
+}
+
+# Return the substage list name of a stage
+# $1:	Stage name
+substage() {
+	echo "sub_stage_${1}"
+}
+
+# Return the hook list name of a substage
+# $1:	Substage name
+hooks() {
+	echo "hooks_${1}"
+}
+
+# Return the substage list of a stage
+# $1:	Stage name
+get_substages() {
+	eval "echo \"\${sub_stage_${1}}\""
+}
+
+# Return the hook list of a substage
+# $1:	Substage name
+get_hooks() {
+	eval "echo \"\${hooks_${1}}\""
+}
+
+# Fill default anchors for a substage
+# $1:	Substage name
+fill_anchors() {
+	eval "${1}=\"${1}_anchor_00 ${1}_anchor_10 ${1}_anchor_20 ${1}_anchor_30 \
+		     ${1}_anchor_40 ${1}_anchor_50 ${1}_anchor_60 ${1}_anchor_70 \
+		     ${1}_anchor_80 ${1}_anchor_90\""
+}
+
+# Apply a patch to OpenWrt's root
+# $1:	Patch file
+apply_patch() {
+	if test -f ${1}; then
+		if test -n "${__use_quilt}"; then
+			exec_log "quilt import \"${1}\""
+			exec_log "quilt push -a"
+		else
+			exec_log "patch -d \"${openwrt_root}\" -p1 -i \"${1}\""
+		fi
+	fi
+}
+
+# Apply patch(es) located in a folder to OpenWrt's root
+# $1:	Path to the folder containing patch file(s)
+apply_patches() {
+	if test -d ${1}; then
+		if test -n "${__use_quilt}"; then
+			find "${1}" -name '*.patch' | sort -n | tac | while read line; do
+				exec_log "quilt import \"${line}\""
+			done
+
+			exec_log "quilt push -a"
+		else
+			find "${1}" -name '*.patch' | sort -n | while read line; do
+				apply_patch "${line}" || return $?
+			done
+		fi
+	fi
+}
+
+# Copy a file/folder to OpenWrt's root
+# $1:	Path to the file/folder to be copied
+# $2:	(Optional) Relative path of OpenWrt root (no / at end)
+copy_file() {
+	if test -f ${1}; then
+		local dest="${openwrt_root}"
+
+		[ -n "${2}" ] && dest="${openwrt_root}/${2}"
+		[ -d "${dest}" ] || exec_log "mkdir -p \"${dest}\""
+		exec_log "cp -af \"${1}\" \"${dest}\""
+	fi
+}
+
+# Copy file(s) from a folder to OpenWrt's root
+# $1:	Path to the folder containing file(s)
+# $2:	(Optional) Relative path of OpenWrt root (no / at end)
+copy_files() {
+	if test -d "${1}"; then
+		local dest="${openwrt_root}"
+
+		[ -n "${2}" ] && dest="${openwrt_root}/${2}"
+		[ -d "${dest}" ] || exec_log "mkdir -p \"${dest}\""
+		exec_log "cp -af \"${1}\"/* \"${dest}/\""
+	fi
+}
+
+# Prepare a file copy
+# Use this if you need to do some modifications to files to be copied before the actual copying procedure
+# $1:	Path to the folder containing file(s)
+# Returns the path of intermediate directory containing files to be copied
+# Use copy_files to finish the copy session
+copy_files_prepare() {
+	local tmpdir=$(mktemp -d -p "${ab_tmp}")
+
+	[ -d "${1}" ] && exec_log "cp -af \"${1}/*\" \"${tmpdir}/\""
+
+	echo "${tmpdir}"
+}
+
+# Remove file/folder
+# $1:	Path to the file/folder relative to OpenWrt's root
+remove_file_folder() {
+	exec_log "rm -rf ${openwrt_root}/${1}" || true
+}
+
+# Remove file(s) listed in a file
+# $1:	Path to the file lists containing file(s) to be removed
+remove_files_from_list() {
+	if test -f "${1}"; then
+		cat "${1}" | while read line; do
+			if test -n "${line}"; then
+				remove_file_folder "${line}"
+			fi
+		done
+	fi
+}
+
+# Scan valid branch tree and print
+# $1:	Parent path
+# $2:	Parent branch name
+# $3:	Depth
+list_all_autobuild_branches_real() {
+	[ ${3} -gt 4 ] && return
+
+	find ${ab_root}/${1} -maxdepth 2 -mindepth 2 -name 'rules' -print | \
+		awk '{n=split($0,a,"/"); print a[n-1]}' | sort | while read line; do
+
+		local depth=$(($3 + 1))
+		local path=
+		local branch=
+
+		if test -z "${1}"; then
+			path=${line}
+		else
+			path=${1}/${line}
+		fi
+
+		if test -z "${2}"; then
+			branch=${line}
+		else
+			branch=${2}-${line}
+		fi
+
+		print_text "${branch}"
+
+		list_all_autobuild_branches_real "${path}" "${branch}" ${depth}
+	done
+}
+
+# List all valid autobuild branches
+list_all_autobuild_branches() {
+	list_all_autobuild_branches_real "" "" 1
+}
diff --git a/autobuild/unified/scripts/get_openwrt_branch.mk b/autobuild/unified/scripts/get_openwrt_branch.mk
new file mode 100644
index 0000000..78592a3
--- /dev/null
+++ b/autobuild/unified/scripts/get_openwrt_branch.mk
@@ -0,0 +1,4 @@
+include include/version.mk
+
+get-version-number:
+	@echo "$(VERSION_NUMBER)"
diff --git a/autobuild/unified/scripts/get_openwrt_defs.mk b/autobuild/unified/scripts/get_openwrt_defs.mk
new file mode 100644
index 0000000..6a80438
--- /dev/null
+++ b/autobuild/unified/scripts/get_openwrt_defs.mk
@@ -0,0 +1,18 @@
+OPENWRT_BUILD = 1
+
+include Makefile
+
+get-staging-dir-root:
+	@echo "$(STAGING_DIR_ROOT)"
+
+get-bin-dir:
+	@echo "$(BIN_DIR)"
+
+get-build-dir:
+	@echo "$(BUILD_DIR)"
+
+get-target-name:
+	@echo "$(BOARD)"
+
+get-subtarget-name:
+	@echo "$(SUBTARGET)"
diff --git a/autobuild/unified/scripts/get_openwrt_kernel_defs.mk b/autobuild/unified/scripts/get_openwrt_kernel_defs.mk
new file mode 100644
index 0000000..7e8678f
--- /dev/null
+++ b/autobuild/unified/scripts/get_openwrt_kernel_defs.mk
@@ -0,0 +1,15 @@
+OPENWRT_BUILD = 1
+
+__AUTOBUILD_TARGET ?= mediatek
+
+include Makefile
+include target/linux/$(__AUTOBUILD_TARGET)/Makefile
+
+get-target-kernel-ver:
+	@echo "$(KERNEL_PATCHVER)"
+
+get-kernel-build-dir:
+	@echo "$(KERNEL_BUILD_DIR)"
+
+get-linux-build-dir:
+	@echo "$(LINUX_DIR)"
diff --git a/autobuild/unified/scripts/kconfig.sh b/autobuild/unified/scripts/kconfig.sh
new file mode 100755
index 0000000..f379ee2
--- /dev/null
+++ b/autobuild/unified/scripts/kconfig.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Helpers for modifying Kconfig-like files
+
+# Disable config(s) in a Kconfig-like file
+# $1:	Kconfig file path
+# $2:	List of config name
+kconfig_disable() {
+	local config_file="${1}"
+	local options="${2}"
+	local option=
+
+	[ -z "${config_file}" ] && return
+
+	for option in ${options}; do
+		eval "sed -i '/^${option}=/d' \"${config_file}\""
+		eval "sed -i '/^# ${option} /d' \"${config_file}\""
+		# eval "sed -i '/^# ${option}$/d' \"${config_file}\""
+		echo "# ${option} is not set" >> ${config_file}
+	done
+}
+
+# Enable config(s) in a Kconfig-like file
+# $1:	Kconfig file path
+# $2:	List of config name
+# $3:	Value to be set for config (y if not specified)
+kconfig_enable() {
+	local config_file="${1}"
+	local options="${2}"
+	local value="${3}"
+	local option=
+
+	[ -z "${config_file}" ] && return
+
+	[ -z "${value}" ] && value=y
+
+	for option in ${options}; do
+		eval "sed -i '/^${option}=/d' \"${config_file}\""
+		eval "sed -i '/^# ${option} /d' \"${config_file}\""
+		# eval "sed -i '/^# ${option}\$/d' \"${config_file}\""
+		echo "${option}=${value}" >> ${config_file}
+	done
+}
+
+# Test whether a config is set in a Kconfig-like file
+# Return 0 if set, 1 is not set
+# $1:	Kconfig file path
+# $2:	Config name
+kconfig_enabled() {
+	local config_file="${1}"
+	local option="${2}"
+
+	[ -z "${config_file}" ] && return 1
+	[ -z "${option}" ] && return 1
+
+	grep "^${option}=" ${config_file} >/dev/null
+}
diff --git a/autobuild/unified/scripts/list.sh b/autobuild/unified/scripts/list.sh
new file mode 100755
index 0000000..25f1fcc
--- /dev/null
+++ b/autobuild/unified/scripts/list.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Helpers for simple list operations
+
+# Check if an element exists in a list
+# $1: list name
+# $2: element name to be checked
+# return 0 if exists, 1 otherwise
+list_find() {
+	local found=
+
+	eval "local list=\"\$${1}\""
+
+	for ele in ${list}; do
+		if test "${ele}" == "${2}"; then
+			found=1
+		fi
+	done
+
+	[ "${found}" = "1" ] && return 0
+
+	return 1
+}
+
+# Delete element(s) from a list
+# $1: list name
+# $2: element name
+list_del() {
+	local tmp_list=
+
+	eval "local list=\"\$${1}\""
+
+	for ele in ${list}; do
+		if test "${ele}" != "${2}"; then
+			tmp_list="${tmp_list} ${ele}"
+		fi
+	done
+
+	eval "${1}=\"\${tmp_list}\""
+}
+
+# Add an element to the end of a list
+# Elements with the same name are allowed
+# $1: list name
+# $2: new element name
+list_append_forced() {
+	eval "${1}=\"\$${1} ${2}\""
+}
+
+# Add an element to the end of a list
+# If the element to be appended already exists, this function does nothing
+# $1: list name
+# $2: new element name
+list_append() {
+	list_find "${1}" "${2}" || list_append_forced "${1}" "${2}"
+}
+
+# Add an element to the end of a list
+# If the element to be appended already exists, this function will remove previous existed element(s)
+# $1: list name
+# $2: new element name
+list_append_unique() {
+	list_find "${1}" "${2}" && list_del "${1}" "${2}"
+	list_append_forced "${1}" "${2}"
+}
+
+# Add an element to the start of a list
+# Elements with the same name are allowed
+# $1: list name
+# $2: new element name
+list_prepend_forced() {
+	eval "${1}=\"${2} \$${1}\""
+}
+
+# Add an element to the start of a list
+# If the element to be prepended already exists, this function does nothing
+# $1: list name
+# $2: new element name
+list_prepend() {
+	list_find "${1}" "${2}" || list_prepend_forced "${1}" "${2}"
+}
+
+# Add an element to the start of a list
+# If the element to be prepended already exists, this function will remove previous existed element(s)
+# $1: list name
+# $2: new element name
+list_prepend_unique() {
+	list_find "${1}" "${2}" && list_del "${1}" "${2}"
+	list_prepend_forced "${1}" "${2}"
+}
+
+# Add an element ahead of an existed element of a list
+# If the target element doesn't exist, this function behaves identical to list_append
+# $1: list name
+# $2: target element name
+# $3: new element name
+list_add_before() {
+	local tmp_list=
+	local added=
+
+	eval "local list=\"\$${1}\""
+
+	for ele in ${list}; do
+		if test -z "${added}" -a "${ele}" == "${2}"; then
+			tmp_list="${tmp_list} ${3}"
+			added=1
+		fi
+
+		tmp_list="${tmp_list} ${ele}"
+	done
+
+	if test "${added}" != "1"; then
+		tmp_list="${tmp_list} ${3}"
+	fi
+
+	eval "${1}=\"\${tmp_list}\""
+}
+
+# Add an element ahead of an existed element of a list
+# If the target element doesn't exist, this function behaves identical to list_append
+# If the target element already exists, this function will remove previous existed element(s)
+# $1: list name
+# $2: target element name
+# $3: new element name
+list_add_before_unique() {
+	list_find "${1}" "${3}" && list_del "${1}" "${3}"
+	list_add_before "${1}" "${2}" "${3}"
+}
+
+# Add an element next to an existed element of a list
+# If the target element doesn't exist, this function behaves identical to list_append
+# $1: list name
+# $2: target element name
+# $3: new element name
+list_add_after() {
+	local tmp_list=
+	local added=
+
+	eval "local list=\"\$${1}\""
+
+	for ele in ${list}; do
+		tmp_list="${tmp_list} ${ele}"
+
+		if test -z "${added}" -a "${ele}" == "${2}"; then
+			tmp_list="${tmp_list} ${3}"
+			added=1
+		fi
+	done
+
+	if test "${added}" != "1"; then
+		tmp_list="${tmp_list} ${3}"
+	fi
+
+	eval "${1}=\"\${tmp_list}\""
+}
+
+# Add an element next to an existed element of a list
+# If the target element doesn't exist, this function behaves identical to list_append
+# If the target element already exists, this function will remove previous existed element(s)
+# $1: list name
+# $2: target element name
+# $3: new element name
+list_add_after_unique() {
+	list_find "${1}" "${3}" && list_del "${1}" "${3}"
+	list_add_after "${1}" "${2}" "${3}"
+}
+
+# Replace element(s) with new one
+# $1: list name
+# $2: target element name
+# $3: new element name
+list_replace() {
+	local tmp_list=
+
+	eval "local list=\"\$${1}\""
+
+	for ele in ${list}; do
+		if test "${ele}" == "${2}"; then
+			tmp_list="${tmp_list} ${3}"
+		else
+			tmp_list="${tmp_list} ${ele}"
+		fi
+	done
+
+	eval "${1}=\"\${tmp_list}\""
+}
diff --git a/autobuild/unified/scripts/log.sh b/autobuild/unified/scripts/log.sh
new file mode 100755
index 0000000..c3f1418
--- /dev/null
+++ b/autobuild/unified/scripts/log.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Helpers for logging
+
+__log_debug_enabled=
+__log_file_stdout=
+__log_file_stderr=
+__stdout=
+__stderr=
+
+[ -L "/proc/$$/fd/1" ] && __stdout=$(readlink "/proc/$$/fd/1")
+[ -L "/proc/$$/fd/2" ] && __stderr=$(readlink "/proc/$$/fd/2")
+
+# Set file prefix where log will be saved to
+# if $2 == 1, log will be saved to ${2}.log,
+# otherwise stdout and stderr will be saved to ${1}.out.log and ${1}.err.log
+# $1:	Log file prefix
+# $2:	Whether to merge stdout and stderr into one file (1 for yes, others for no)
+set_log_file_prefix() {
+	if test -z "${1}"; then
+		__log_file_stdout=
+		__log_file_stderr=
+	fi
+
+	if test "x${2}" = x1; then
+		__log_file_stdout="${1}.log"
+		__log_file_stderr="${__log_file_stdout}"
+	else
+		__log_file_stdout="${1}.out.log"
+		__log_file_stderr="${1}.err.log"
+	fi
+
+	rm -f "${__log_file_stdout}" "${__log_file_stderr}"
+}
+
+# Enable logging debug information
+enable_log_debug() {
+	__log_debug_enabled=1
+}
+
+# Save text into log file if possible
+# $1:	Message
+__log_text() {
+	if test -n "${__log_file_stdout}"; then
+		echo -e "${1}" >> ${__log_file_stdout}
+	fi
+
+	if test -n "${__log_file_stderr}" -a x"${__log_file_stdout}" != x"${__log_file_stderr}"; then
+		echo -e "${1}" >> ${__log_file_stderr}
+	fi
+}
+
+# Print text to console.
+# if stderr != stdout, print text again for stderr
+# $1:	Message
+__con_print_text() {
+	echo -e "${1}"
+
+	if test -n "${__stderr}" -a x"${__stdout}" != x"${__stderr}"; then
+		echo -e "${1}" 1>&2
+	fi
+}
+
+# Print error text without prefix
+# $1:	Error message
+log_err_raw() {
+	local text="${1}"
+
+	__con_print_text "\033[93;41m${text}\033[0m" 1>&2
+	__log_text "${text}"
+}
+
+# Print error text
+# $1:	Error message
+log_err() {
+	local text="ERROR: ${1}"
+
+	log_err_raw "${text}"
+}
+
+# Print warning text without prefix
+# $1:	Warning message
+log_warn_raw() {
+	local text="${1}"
+
+	__con_print_text "\033[1;31m${text}\033[0m"
+	__log_text "${text}"
+}
+
+# Print warning text
+# $1:	Warning message
+log_warn() {
+	local text="WARN: ${1}"
+
+	log_warn_raw "${text}"
+}
+
+# Print information text without prefix
+# $1:	Information message
+log_info_raw() {
+	local text="${1}"
+
+	__con_print_text "\033[1;36m${text}\033[0m"
+	__log_text "${text}"
+}
+
+# Print information text
+# $1:	Information message
+log_info() {
+	local text="INFO: ${1}"
+
+	log_info_raw "${text}"
+}
+
+# Print debugging text
+# $1:	Debuging message
+log_dbg() {
+	local text="DEBUG: ${1}"
+
+	[ x"${__log_debug_enabled}" != x"1" ] && return
+
+	__con_print_text "${text}"
+	__log_text "${text}"
+}
+
+# Print stage text
+# $1:	Stage message
+prompt_stage() {
+	__con_print_text "\n\033[47;30m${1}\033[0m"
+	__log_text "\n${1}"
+}
+
+# Print config text (stdout only)
+# $1:	Config name
+# $2:	Config value
+print_conf() {
+	__con_print_text "\033[1m${1}: \033[4m${2}\033[0m"
+	__log_text "${1}: ${2}"
+}
+
+# Print text
+# $1:	Text
+print_text() {
+	__con_print_text "${1}"
+	__log_text "${1}"
+}
+
+# Execute command and Save its output to log file(s)
+# $1:	Command line
+# $2:	No log to file
+# Return the exit code of the command line (may not be accurate for compond commands)
+exec_log() {
+	local ret=
+
+	[ -z "${1}" ] && return
+
+	local expanded=$(eval "echo ${1}")
+	print_text "+ ${expanded}"
+
+	if test -n "${2}"; then
+		eval "${1}"
+		return $?
+	fi
+
+	if test -n "${__log_file_stdout}" -a -n "${__log_file_stderr}"; then
+		eval "{ ${1} 3>&1 1>&2 2>&3 3>&- | tee -a \"${__log_file_stderr}\"; [ \${PIPESTATUS[0]} = 0 ] && true || false; } 3>&1 1>&2 2>&3 3>&- | tee -a \"${__log_file_stdout}\"; ret=\${PIPESTATUS[0]}"
+	elif test -n "${__log_file_stdout}"; then
+		eval "{ ${1} 3>&1 1>&2 2>&3 3>&- | tee -a \"${__log_file_stdout}\"; [ \${PIPESTATUS[0]} = 0 ] && true || false; } 3>&1 1>&2 2>&3 3>&- | tee -a \"${__log_file_stdout}\"; ret=\${PIPESTATUS[0]}"
+	else
+		# No logging required
+		eval "${1}; ret=\$?"
+	fi
+
+	return ${ret}
+}
diff --git a/autobuild/unified/scripts/master/scripts-feeds-support-subdir.patch b/autobuild/unified/scripts/master/scripts-feeds-support-subdir.patch
new file mode 100644
index 0000000..952ef5c
--- /dev/null
+++ b/autobuild/unified/scripts/master/scripts-feeds-support-subdir.patch
@@ -0,0 +1,49 @@
+--- a/scripts/feeds
++++ b/scripts/feeds
+@@ -120,16 +120,22 @@ sub update_location($$)
+ 	return 0;
+ }
+ 
+-sub update_index($)
++sub update_index($$)
+ {
+ 	my $name = shift;
++	my $subdir = shift;
++	my $real_subdir = "";
+ 
+ 	-d "./feeds/$name.tmp" or mkdir "./feeds/$name.tmp" or return 1;
+ 	-d "./feeds/$name.tmp/info" or mkdir "./feeds/$name.tmp/info" or return 1;
+ 
++	if (length( $subdir || '' )) {
++		$real_subdir = "/$subdir";
++	}
++
+ 	system("$mk -s prepare-mk OPENWRT_BUILD= TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+-	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"packageinfo\" SCAN_DIR=\"feeds/$name\" SCAN_NAME=\"package\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+-	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"targetinfo\" SCAN_DIR=\"feeds/$name\" SCAN_NAME=\"target\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" SCAN_MAKEOPTS=\"TARGET_BUILD=1\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
++	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"packageinfo\" SCAN_DIR=\"feeds/$name$real_subdir\" SCAN_NAME=\"package\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
++	system("$mk -s -f include/scan.mk IS_TTY=1 SCAN_TARGET=\"targetinfo\" SCAN_DIR=\"feeds/$name$real_subdir\" SCAN_NAME=\"target\" SCAN_DEPTH=5 SCAN_EXTRA=\"\" SCAN_MAKEOPTS=\"TARGET_BUILD=1\" TMP_DIR=\"$ENV{TOPDIR}/feeds/$name.tmp\"");
+ 	system("ln -sf $name.tmp/.packageinfo ./feeds/$name.index");
+ 	system("ln -sf $name.tmp/.targetinfo ./feeds/$name.targetindex");
+ 
+@@ -837,15 +843,17 @@ sub update {
+ 	my @index_feeds;
+ 	foreach my $feed (@feeds) {
+ 		my ($type, $name, $src) = @$feed;
++		my $subdir = $feed->[3]{subdir};
+ 		next unless $#ARGV == -1 or $opts{a} or $argv_feeds{$name};
+ 		if (not $opts{i}) {
+ 			update_feed($type, $name, $src, $opts{f}) == 0 or $failed=1;
+ 		}
+-		push @index_feeds, $name;
++		push @index_feeds, [ $name, $subdir ];
+ 	}
+-	foreach my $name (@index_feeds) {
++	foreach my $index_feed (@index_feeds) {
++		my ($name, $subdir) = @$index_feed;
+ 		warn "Create index file './feeds/$name.index' \n";
+-		update_index($name) == 0 or do {
++		update_index($name, $subdir) == 0 or do {
+ 			warn "failed.\n";
+ 			$failed=1;
+ 		};
diff --git a/autobuild/unified/scripts/openwrt_helpers.sh b/autobuild/unified/scripts/openwrt_helpers.sh
new file mode 100755
index 0000000..86ca6f1
--- /dev/null
+++ b/autobuild/unified/scripts/openwrt_helpers.sh
@@ -0,0 +1,326 @@
+#!/bin/sh
+
+# Copyright (C) 2024 MediaTek Inc. All rights reserved.
+# Author: Weijie Gao <weijie.gao@mediatek.com>
+# Helpers for OpenWrt
+
+# Get OpenWrt's kernel version of a specific target
+# $1:	Target name (optional)
+openwrt_get_target_kernel_version() {
+	local target=
+
+	[ -n "${1}" ] && target="__AUTOBUILD_TARGET=${1}"
+
+	make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_kernel_defs.mk" get-target-kernel-ver "${target}"
+}
+
+# Get OpenWrt's linux kernel build directory of a specific target
+# $1:	Target name (optional)
+openwrt_get_target_kernel_linux_build_dir() {
+	local target=
+
+	[ -n "${1}" ] && target="__AUTOBUILD_TARGET=${1}"
+
+	make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_kernel_defs.mk" get-linux-build-dir "${target}"
+}
+
+# Get OpenWrt's branch
+openwrt_get_branch() {
+	local version=$(make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_branch.mk" get-version-number)
+
+	if test x"${version}" = x"SNAPSHOT"; then
+		echo "master"
+		return
+	fi
+
+	echo "${version}" | sed 's/\([0-9][0-9]\.[0-9][0-9]\).*/\1/g'
+}
+
+# Get OpenWrt's staging_dir root path
+openwrt_get_staging_dir_root() {
+	make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_defs.mk" get-staging-dir-root
+}
+
+# Get OpenWrt's bin path
+openwrt_get_bin_dir() {
+	make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_defs.mk" get-bin-dir
+}
+
+# Get OpenWrt's target name
+openwrt_get_target_name() {
+	make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_defs.mk" get-target-name
+}
+
+# Get OpenWrt's subtarget name
+openwrt_get_subtarget_name() {
+	make -s -C "${openwrt_root}" -f "${ab_root}/scripts/get_openwrt_defs.mk" get-subtarget-name
+}
+
+# Check if a path is OpenWrt's root directory
+# $1:	Path to be checked
+is_openwrt_build_root() {
+	[ -z "${1}" ] && return 1
+
+	[ -d "${1}/include" -a -d "${1}/package" -a -d "${1}/scripts" -a -d "${1}/target" \
+		-a -d "${1}/toolchain" -a -d "${1}/tools" -a -f "${1}/Config.in" -a -f "${1}/Makefile" \
+		-a -f "${1}/rules.mk" -a -f "${1}/scripts/kconfig.pl" ]
+}
+
+# Parse a feed line
+# $1:	Line
+# $2:	Name of a array to store the contents
+#	[0] => (1: disabled; otherwise enabled)
+#	[1] => type
+#	[2] => flags
+#	[3] => name
+#	[4] => url
+#	[5] => branch/revision/none selection
+#	[6] => branch/revision
+__openwrt_feed_line_decompose() {
+	local item_idx=0
+	local arr_idx=0
+	local flag_idx=0
+
+	eval "${2}[0]="	# default enabled
+
+	for item in ${1}; do
+		if test "${item_idx}" -eq 0; then
+			if test x"${item}" = x"#"; then
+				eval "${2}[0]=1"	# disabled
+				arr_idx=1
+			elif test x"${item:0:1}" == x"#"; then
+				eval "${2}[0]=1"	# disabled
+				eval "${2}[1]=\"${item:1}\""	# type
+				arr_idx=2
+			else
+				eval "${2}[1]=\"${item}\""	# type
+				arr_idx=2
+			fi
+		else
+			if test "${arr_idx}" -eq 1; then
+				eval "${2}[1]=\"${item}\""	# type
+				arr_idx=2
+			elif test "${arr_idx}" -eq 2; then
+				if test x"${item:0:2}" == x"--"; then
+					if test ${flag_idx} -eq 0; then
+						eval "${2}[2]=\"${item}\""	# flags
+						flag_idx=1
+					else
+						eval "${2}[2]=\"\${${2}[2]} ${item}\""	# flags
+					fi
+				else
+					eval "${2}[3]=\"${item}\""	# name
+					arr_idx=4
+				fi
+			elif test "${arr_idx}" -eq 4; then
+				local url_branch=${item%;*}
+				local branch=${item##*;}
+				local url_revision=${item%^*}
+				local revision=${item##*^}
+
+				if test -n "${branch}" -a x"${url_branch}" != x"${branch}"; then
+					eval "${2}[4]=\"${url_branch}\""
+					eval "${2}[5]=\";\""
+					eval "${2}[6]=\"${branch}\""
+				elif test -n "${revision}" -a x"${url_revision}" != x"${revision}"; then
+					eval "${2}[4]=\"${url_revision}\""
+					eval "${2}[5]=\"^\""
+					eval "${2}[6]=\"${revision}\""
+				else
+					eval "${2}[4]=\"${item}\""
+					eval "${2}[5]="
+					eval "${2}[6]="
+				fi
+
+				arr_idx=7
+				break
+			fi
+		fi
+
+		item_idx=1
+	done
+
+	[ "${arr_idx}" -lt 7 ] && return 1
+
+	return 0
+}
+
+# Assemble a feed line
+# $1:	Array of the contents
+# Return: Assembled line
+__openwrt_feed_line_compose() {
+	local new_line=
+	local arr=()
+
+	for i in `seq 0 6`; do
+		eval "arr[$i]=\"\${${1}[$i]}\""
+	done
+
+	[ -z "${arr[1]}" -o -z "${arr[3]}" -o -z "${arr[4]}" ] && return 1
+
+	if test x"${arr[0]}" = x"1"; then
+		new_line="# "
+	fi
+
+	new_line="${new_line}${arr[1]} "
+
+	if test -n "${arr[2]}"; then
+		new_line="${new_line}${arr[2]} "
+	fi
+
+	new_line="${new_line}${arr[3]} ${arr[4]}"
+
+	if test x"${arr[5]}" = x";" -o x"${arr[5]}" = x"^"; then
+		if test -n "${arr[6]}"; then
+			new_line="${new_line}${arr[5]}${arr[6]}"
+		fi
+	fi
+
+	echo "${new_line}"
+
+	return 0
+}
+
+# Add new feed to OpenWrt's feeds
+# Existed name-matched lines will be removed
+# $1:	Name
+# $2:	type
+# $3:	URL (Optional with Branch/Revision)
+# $4:	(Optional) Flags
+openwrt_feeds_add() {
+	local new_line=
+	local farr=()
+
+	farr[0]=
+	farr[1]="${2}"
+	farr[2]=
+	farr[3]="${1}"
+	farr[4]="${3}"
+	farr[5]=
+	farr[6]=
+
+	shift 3
+	farr[2]="$@"
+
+	new_line=$(__openwrt_feed_line_compose farr)
+
+	openwrt_feeds_remove "${1}"
+
+	echo "${new_line}" >> "${openwrt_root}/feeds.conf.default"
+}
+
+# Replace OpenWrt's feeds repo URL
+# $1:	Name
+# $2:	New URL
+openwrt_feeds_replace_url() {
+	rm -f "${ab_root}/feeds.conf.mtk"
+
+	cat "${openwrt_root}/feeds.conf.default" | while read line; do
+		local farr=()
+
+		if __openwrt_feed_line_decompose "${line}" farr; then
+			if test x"${farr[3]}" = x"${1}"; then
+				farr[4]="${2}"
+
+				line=$(__openwrt_feed_line_compose farr)
+			fi
+		fi
+
+		echo "${line}" >> "${ab_root}/feeds.conf.mtk"
+	done
+
+	mv "${ab_root}/feeds.conf.mtk" "${openwrt_root}/feeds.conf.default"
+}
+
+# Change OpenWrt's feeds repo git mode
+# $1:	Name
+# $2:	Set to 1 to use src-git-full, otherwise src-git
+openwrt_feeds_change_src_git_type() {
+	rm -f "${ab_root}/feeds.conf.mtk"
+
+	cat "${openwrt_root}/feeds.conf.default" | while read line; do
+		local farr=()
+
+		if __openwrt_feed_line_decompose "${line}" farr; then
+			if test x"${farr[3]}" = x"${1}"; then
+				if test x"${farr[1]}" = x"src-git" -o x"${farr[1]}" = x"src-git-full"; then
+					if test ${2} -eq 1; then
+						farr[1]="src-git-full"
+					else
+						farr[1]="src-git"
+					fi
+				fi
+
+				line=$(__openwrt_feed_line_compose farr)
+			fi
+		fi
+
+		echo "${line}" >> "${ab_root}/feeds.conf.mtk"
+	done
+
+	mv "${ab_root}/feeds.conf.mtk" "${openwrt_root}/feeds.conf.default"
+}
+
+# Remove OpenWrt's feeds
+# $1:	Name
+openwrt_feeds_remove() {
+	rm -f "${ab_root}/feeds.conf.mtk"
+
+	cat "${openwrt_root}/feeds.conf.default" | while read line; do
+		local farr=()
+
+		if __openwrt_feed_line_decompose "${line}" farr; then
+			if test x"${farr[3]}" = x"${1}"; then
+				continue
+			fi
+		fi
+
+		echo "${line}" >> "${ab_root}/feeds.conf.mtk"
+	done
+
+	mv "${ab_root}/feeds.conf.mtk" "${openwrt_root}/feeds.conf.default"
+}
+
+# Disable OpenWrt's feeds
+# $1:	Name
+openwrt_feeds_disable() {
+	rm -f "${ab_root}/feeds.conf.mtk"
+
+	cat "${openwrt_root}/feeds.conf.default" | while read line; do
+		local farr=()
+
+		if __openwrt_feed_line_decompose "${line}" farr; then
+			if test x"${farr[3]}" = x"${1}" -a x"${farr[0]}" != x"1"; then
+				farr[0]=1
+
+				line=$(__openwrt_feed_line_compose farr)
+			fi
+		fi
+
+		echo "${line}" >> "${ab_root}/feeds.conf.mtk"
+	done
+
+	mv "${ab_root}/feeds.conf.mtk" "${openwrt_root}/feeds.conf.default"
+}
+
+# Enable OpenWrt's feeds
+# $1:	Name
+openwrt_feeds_enable() {
+	rm -f "${ab_root}/feeds.conf.mtk"
+
+	cat "${openwrt_root}/feeds.conf.default" | while read line; do
+		local farr=()
+
+		if __openwrt_feed_line_decompose "${line}" farr; then
+			if test x"${farr[3]}" = x"${1}" -a x"${farr[0]}" = x"1"; then
+				farr[0]=
+
+				line=$(__openwrt_feed_line_compose farr)
+			fi
+		fi
+
+		echo "${line}" >> "${ab_root}/feeds.conf.mtk"
+	done
+
+	mv "${ab_root}/feeds.conf.mtk" "${openwrt_root}/feeds.conf.default"
+}
diff --git a/autobuild/unified/scripts/openwrt_kconfig.mk b/autobuild/unified/scripts/openwrt_kconfig.mk
new file mode 100644
index 0000000..4c1e930
--- /dev/null
+++ b/autobuild/unified/scripts/openwrt_kconfig.mk
@@ -0,0 +1,20 @@
+
+include Makefile
+
+savedefconfig: scripts/config/conf prepare-tmpinfo FORCE
+	[ -e .config ] && { \
+		$< --$@=$(if $(CONFIG_FILE),$(CONFIG_FILE),defconfig) Config.in && \
+			printf "Default config file saved to defconfig\n"; \
+	} || { \
+		printf ".config not exist!\n" >&2; \
+		false; \
+	}
+
+loaddefconfig: scripts/config/conf prepare-tmpinfo FORCE
+	[ -e "$(CONFIG_FILE)" ] && { \
+		[ -L .config ] && export KCONFIG_OVERWRITECONFIG=1; \
+			$< --defconfig=$(CONFIG_FILE) Config.in; \
+	} || { \
+		printf "Default config file not specified by CONFIG_FILE= !\n" >&2; \
+		false; \
+	}