New Upstream Release - opensbi

Ready changes

Summary

Merged new upstream version: 1.2 (was: 1.1).

Diff

diff --git a/.gitignore b/.gitignore
index 534ad64..c11afd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@ install/
 
 # Development friendly files
 tags
+cscope*
+*.swp
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..acfc138
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+mainmenu "OpenSBI $(OPENSBI_PLATFORM) Configuration"
+
+config OPENSBI_SRC_DIR
+	string
+	option env="OPENSBI_SRC_DIR"
+
+config OPENSBI_PLATFORM
+	string
+	option env="OPENSBI_PLATFORM"
+
+config OPENSBI_PLATFORM_SRC_DIR
+	string
+	option env="OPENSBI_PLATFORM_SRC_DIR"
+
+menu "Platform Options"
+source "$(OPENSBI_PLATFORM_SRC_DIR)/Kconfig"
+endmenu
+
+source "$(OPENSBI_SRC_DIR)/lib/sbi/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/firmware/Kconfig"
diff --git a/Makefile b/Makefile
index f619ef7..92203c5 100644
--- a/Makefile
+++ b/Makefile
@@ -47,11 +47,14 @@ ifdef PLATFORM_DIR
     platform_parent_dir=$(platform_dir_path)
   else
     PLATFORM=$(shell basename $(platform_dir_path))
-    platform_parent_dir=$(subst $(PLATFORM),,$(platform_dir_path))
+    platform_parent_dir=$(shell realpath ${platform_dir_path}/..)
   endif
 else
  platform_parent_dir=$(src_dir)/platform
 endif
+ifndef PLATFORM_DEFCONFIG
+PLATFORM_DEFCONFIG=defconfig
+endif
 
 # Check if verbosity is ON for build process
 CMD_PREFIX_DEFAULT := @
@@ -70,6 +73,20 @@ export libsbi_dir=$(CURDIR)/lib/sbi
 export libsbiutils_dir=$(CURDIR)/lib/utils
 export firmware_dir=$(CURDIR)/firmware
 
+# Setup variables for kconfig
+ifdef PLATFORM
+export PYTHONDONTWRITEBYTECODE=1
+export KCONFIG_DIR=$(platform_build_dir)/kconfig
+export KCONFIG_AUTOLIST=$(KCONFIG_DIR)/auto.list
+export KCONFIG_AUTOHEADER=$(KCONFIG_DIR)/autoconf.h
+export KCONFIG_AUTOCMD=$(KCONFIG_DIR)/auto.conf.cmd
+export KCONFIG_CONFIG=$(KCONFIG_DIR)/.config
+# Additional exports for include paths in Kconfig files
+export OPENSBI_SRC_DIR=$(src_dir)
+export OPENSBI_PLATFORM=$(PLATFORM)
+export OPENSBI_PLATFORM_SRC_DIR=$(platform_src_dir)
+endif
+
 # Find library version
 OPENSBI_VERSION_MAJOR=`grep "define OPENSBI_VERSION_MAJOR" $(include_dir)/sbi/sbi_version.h | sed 's/.*MAJOR.*\([0-9][0-9]*\)/\1/'`
 OPENSBI_VERSION_MINOR=`grep "define OPENSBI_VERSION_MINOR" $(include_dir)/sbi/sbi_version.h | sed 's/.*MINOR.*\([0-9][0-9]*\)/\1/'`
@@ -183,12 +200,38 @@ libsbi-object-mks=$(shell if [ -d $(libsbi_dir) ]; then find $(libsbi_dir) -inam
 libsbiutils-object-mks=$(shell if [ -d $(libsbiutils_dir) ]; then find $(libsbiutils_dir) -iname "objects.mk" | sort -r; fi)
 firmware-object-mks=$(shell if [ -d $(firmware_dir) ]; then find $(firmware_dir) -iname "objects.mk" | sort -r; fi)
 
-# Include platform specifig config.mk
+# The "make all" rule should always be first rule
+.PHONY: all
+all:
+
+# Include platform specific .config
 ifdef PLATFORM
-include $(platform_src_dir)/config.mk
+.PHONY: menuconfig
+menuconfig: $(platform_src_dir)/Kconfig $(src_dir)/Kconfig
+	$(CMD_PREFIX)mkdir -p $(KCONFIG_DIR)
+	$(CMD_PREFIX)$(src_dir)/scripts/Kconfiglib/menuconfig.py $(src_dir)/Kconfig
+	$(CMD_PREFIX)$(src_dir)/scripts/Kconfiglib/genconfig.py --header-path $(KCONFIG_AUTOHEADER) --sync-deps $(KCONFIG_DIR) --file-list $(KCONFIG_AUTOLIST) $(src_dir)/Kconfig
+
+.PHONY: savedefconfig
+savedefconfig: $(platform_src_dir)/Kconfig $(src_dir)/Kconfig
+	$(CMD_PREFIX)mkdir -p $(KCONFIG_DIR)
+	$(CMD_PREFIX)$(src_dir)/scripts/Kconfiglib/savedefconfig.py --kconfig $(src_dir)/Kconfig --out $(KCONFIG_DIR)/defconfig
+
+$(KCONFIG_CONFIG): $(platform_src_dir)/configs/$(PLATFORM_DEFCONFIG) $(platform_src_dir)/Kconfig $(src_dir)/Kconfig
+	$(CMD_PREFIX)mkdir -p $(KCONFIG_DIR)
+	$(CMD_PREFIX)$(src_dir)/scripts/Kconfiglib/defconfig.py --kconfig $(src_dir)/Kconfig $(platform_src_dir)/configs/$(PLATFORM_DEFCONFIG)
+	$(CMD_PREFIX)$(src_dir)/scripts/Kconfiglib/genconfig.py --header-path $(KCONFIG_AUTOHEADER) --sync-deps $(KCONFIG_DIR) --file-list $(KCONFIG_AUTOLIST) $(src_dir)/Kconfig
+
+$(KCONFIG_AUTOCMD): $(KCONFIG_CONFIG)
+	$(CMD_PREFIX)mkdir -p $(KCONFIG_DIR)
+	$(CMD_PREFIX)printf "%s: " $(KCONFIG_CONFIG) > $(KCONFIG_AUTOCMD)
+	$(CMD_PREFIX)cat $(KCONFIG_AUTOLIST) | tr '\n' ' ' >> $(KCONFIG_AUTOCMD)
+
+include $(KCONFIG_CONFIG)
+include $(KCONFIG_AUTOCMD)
 endif
 
-# Include all object.mk files
+# Include all objects.mk files
 ifdef PLATFORM
 include $(platform-object-mks)
 endif
@@ -198,8 +241,8 @@ include $(firmware-object-mks)
 
 # Setup list of objects
 libsbi-objs-path-y=$(foreach obj,$(libsbi-objs-y),$(build_dir)/lib/sbi/$(obj))
-libsbiutils-objs-path-y=$(foreach obj,$(libsbiutils-objs-y),$(build_dir)/lib/utils/$(obj))
 ifdef PLATFORM
+libsbiutils-objs-path-y=$(foreach obj,$(libsbiutils-objs-y),$(platform_build_dir)/lib/utils/$(obj))
 platform-objs-path-y=$(foreach obj,$(platform-objs-y),$(platform_build_dir)/$(obj))
 firmware-bins-path-y=$(foreach bin,$(firmware-bins-y),$(platform_build_dir)/firmware/$(bin))
 endif
@@ -280,6 +323,9 @@ ifeq ($(BUILD_INFO),y)
 GENFLAGS	+=	-DOPENSBI_BUILD_TIME_STAMP="\"$(OPENSBI_BUILD_TIME_STAMP)\""
 GENFLAGS	+=	-DOPENSBI_BUILD_COMPILER_VERSION="\"$(OPENSBI_BUILD_COMPILER_VERSION)\""
 endif
+ifdef PLATFORM
+GENFLAGS	+=	-include $(KCONFIG_AUTOHEADER)
+endif
 GENFLAGS	+=	$(libsbiutils-genflags-y)
 GENFLAGS	+=	$(platform-genflags-y)
 GENFLAGS	+=	$(firmware-genflags-y)
@@ -413,29 +459,33 @@ compile_gen_dep = $(CMD_PREFIX)mkdir -p `dirname $(1)`; \
 	     echo "$(1:.dep=$(2)): $(3)" >> $(1)
 
 targets-y  = $(build_dir)/lib/libsbi.a
-targets-y  += $(build_dir)/lib/libsbiutils.a
 ifdef PLATFORM
 targets-y += $(platform_build_dir)/lib/libplatsbi.a
 endif
 targets-y += $(firmware-bins-path-y)
 
-# Default rule "make" should always be first rule
+# The default "make all" rule
 .PHONY: all
 all: $(targets-y)
 
 # Preserve all intermediate files
 .SECONDARY:
 
+# Rules for lib/sbi sources
 $(build_dir)/lib/libsbi.a: $(libsbi-objs-path-y)
 	$(call compile_ar,$@,$^)
 
-$(build_dir)/lib/libsbiutils.a: $(libsbi-objs-path-y) $(libsbiutils-objs-path-y)
-	$(call compile_ar,$@,$^)
-
 $(platform_build_dir)/lib/libplatsbi.a: $(libsbi-objs-path-y) $(libsbiutils-objs-path-y) $(platform-objs-path-y)
 	$(call compile_ar,$@,$^)
 
-$(build_dir)/%.dep: $(src_dir)/%.c
+$(build_dir)/%.dep: $(src_dir)/%.carray $(KCONFIG_CONFIG)
+	$(call compile_gen_dep,$@,.c,$< $(KCONFIG_CONFIG))
+	$(call compile_gen_dep,$@,.o,$(@:.dep=.c))
+
+$(build_dir)/%.c: $(src_dir)/%.carray
+	$(call compile_carray,$@,$<)
+
+$(build_dir)/%.dep: $(src_dir)/%.c $(KCONFIG_CONFIG)
 	$(call compile_cc_dep,$@,$<)
 
 $(build_dir)/%.o: $(src_dir)/%.c
@@ -449,32 +499,24 @@ $(build_dir)/lib/sbi/sbi_init.o: $(libsbi_dir)/sbi_init.c FORCE
 	$(call compile_cc,$@,$<)
 endif
 
-$(build_dir)/%.dep: $(src_dir)/%.S
+$(build_dir)/%.dep: $(src_dir)/%.S $(KCONFIG_CONFIG)
 	$(call compile_as_dep,$@,$<)
 
 $(build_dir)/%.o: $(src_dir)/%.S
 	$(call compile_as,$@,$<)
 
-$(build_dir)/%.dep: $(src_dir)/%.carray
-	$(call compile_gen_dep,$@,.c,$<)
+# Rules for platform sources
+$(platform_build_dir)/%.dep: $(platform_src_dir)/%.carray $(KCONFIG_CONFIG)
+	$(call compile_gen_dep,$@,.c,$< $(KCONFIG_CONFIG))
 	$(call compile_gen_dep,$@,.o,$(@:.dep=.c))
 
-$(build_dir)/%.c: $(src_dir)/%.carray
+$(platform_build_dir)/%.c: $(platform_src_dir)/%.carray
 	$(call compile_carray,$@,$<)
 
-$(platform_build_dir)/%.bin: $(platform_build_dir)/%.elf
-	$(call compile_objcopy,$@,$<)
-
-$(platform_build_dir)/%.elf: $(platform_build_dir)/%.o $(platform_build_dir)/%.elf.ld $(platform_build_dir)/lib/libplatsbi.a
-	$(call compile_elf,$@,$@.ld,$< $(platform_build_dir)/lib/libplatsbi.a)
-
-$(platform_build_dir)/%.ld: $(src_dir)/%.ldS
-	$(call compile_cpp,$@,$<)
-
-$(platform_build_dir)/%.dep: $(platform_src_dir)/%.c
+$(platform_build_dir)/%.dep: $(platform_src_dir)/%.c $(KCONFIG_CONFIG)
 	$(call compile_cc_dep,$@,$<)
 
-$(platform_build_dir)/%.o: $(platform_src_dir)/%.c
+$(platform_build_dir)/%.o: $(platform_src_dir)/%.c $(KCONFIG_CONFIG)
 	$(call compile_cc,$@,$<)
 
 $(platform_build_dir)/%.dep: $(platform_src_dir)/%.S
@@ -483,8 +525,8 @@ $(platform_build_dir)/%.dep: $(platform_src_dir)/%.S
 $(platform_build_dir)/%.o: $(platform_src_dir)/%.S
 	$(call compile_as,$@,$<)
 
-$(platform_build_dir)/%.dep: $(platform_src_dir)/%.dts
-	$(call compile_gen_dep,$@,.dtb,$<)
+$(platform_build_dir)/%.dep: $(platform_src_dir)/%.dts $(KCONFIG_CONFIG)
+	$(call compile_gen_dep,$@,.dtb,$< $(KCONFIG_CONFIG))
 	$(call compile_gen_dep,$@,.c,$(@:.dep=.dtb))
 	$(call compile_gen_dep,$@,.o,$(@:.dep=.c))
 
@@ -494,13 +536,30 @@ $(platform_build_dir)/%.c: $(platform_build_dir)/%.dtb
 $(platform_build_dir)/%.dtb: $(platform_src_dir)/%.dts
 	$(call compile_dts,$@,$<)
 
-$(platform_build_dir)/%.dep: $(src_dir)/%.c
+# Rules for lib/utils and firmware sources
+$(platform_build_dir)/%.bin: $(platform_build_dir)/%.elf
+	$(call compile_objcopy,$@,$<)
+
+$(platform_build_dir)/%.elf: $(platform_build_dir)/%.o $(platform_build_dir)/%.elf.ld $(platform_build_dir)/lib/libplatsbi.a
+	$(call compile_elf,$@,$@.ld,$< $(platform_build_dir)/lib/libplatsbi.a)
+
+$(platform_build_dir)/%.ld: $(src_dir)/%.ldS
+	$(call compile_cpp,$@,$<)
+
+$(platform_build_dir)/%.dep: $(src_dir)/%.carray $(KCONFIG_CONFIG)
+	$(call compile_gen_dep,$@,.c,$< $(KCONFIG_CONFIG))
+	$(call compile_gen_dep,$@,.o,$(@:.dep=.c))
+
+$(platform_build_dir)/%.c: $(src_dir)/%.carray
+	$(call compile_carray,$@,$<)
+
+$(platform_build_dir)/%.dep: $(src_dir)/%.c $(KCONFIG_CONFIG)
 	$(call compile_cc_dep,$@,$<)
 
 $(platform_build_dir)/%.o: $(src_dir)/%.c
 	$(call compile_cc,$@,$<)
 
-$(platform_build_dir)/%.dep: $(src_dir)/%.S
+$(platform_build_dir)/%.dep: $(src_dir)/%.S $(KCONFIG_CONFIG)
 	$(call compile_as_dep,$@,$<)
 
 $(platform_build_dir)/%.o: $(src_dir)/%.S
@@ -544,7 +603,6 @@ endif
 endif
 
 install_targets-y  = install_libsbi
-install_targets-y  += install_libsbiutils
 ifdef PLATFORM
 install_targets-y += install_libplatsbi
 install_targets-y += install_firmwares
@@ -559,17 +617,12 @@ install_libsbi: $(build_dir)/lib/libsbi.a
 	$(call inst_header_dir,$(install_root_dir)/$(install_include_path),$(include_dir)/sbi)
 	$(call inst_file,$(install_root_dir)/$(install_lib_path)/libsbi.a,$(build_dir)/lib/libsbi.a)
 
-.PHONY: install_libsbiutils
-install_libsbiutils: $(build_dir)/lib/libsbiutils.a
-	$(call inst_header_dir,$(install_root_dir)/$(install_include_path),$(include_dir)/sbi_utils)
-	$(call inst_file,$(install_root_dir)/$(install_lib_path)/libsbiutils.a,$(build_dir)/lib/libsbiutils.a)
-
 .PHONY: install_libplatsbi
-install_libplatsbi: $(platform_build_dir)/lib/libplatsbi.a $(build_dir)/lib/libsbi.a $(build_dir)/lib/libsbiutils.a
+install_libplatsbi: $(platform_build_dir)/lib/libplatsbi.a $(build_dir)/lib/libsbi.a
 	$(call inst_file,$(install_root_dir)/$(install_lib_path)/opensbi/$(platform_subdir)/lib/libplatsbi.a,$(platform_build_dir)/lib/libplatsbi.a)
 
 .PHONY: install_firmwares
-install_firmwares: $(platform_build_dir)/lib/libplatsbi.a $(build_dir)/lib/libsbi.a $(build_dir)/lib/libsbiutils.a $(firmware-bins-path-y)
+install_firmwares: $(platform_build_dir)/lib/libplatsbi.a $(build_dir)/lib/libsbi.a $(firmware-bins-path-y)
 	$(call inst_file_list,$(install_root_dir),$(build_dir),$(install_firmware_path)/$(platform_subdir)/firmware,$(firmware-elfs-path-y))
 	$(call inst_file_list,$(install_root_dir),$(build_dir),$(install_firmware_path)/$(platform_subdir)/firmware,$(firmware-bins-path-y))
 
@@ -577,6 +630,17 @@ install_firmwares: $(platform_build_dir)/lib/libplatsbi.a $(build_dir)/lib/libsb
 install_docs: $(build_dir)/docs/latex/refman.pdf
 	$(call inst_file,$(install_root_dir)/$(install_docs_path)/refman.pdf,$(build_dir)/docs/latex/refman.pdf)
 
+.PHONY: cscope
+cscope:
+	$(CMD_PREFIX)find \
+		"$(src_dir)/firmware" \
+		"$(src_dir)/include" \
+		"$(src_dir)/lib" \
+		"$(platform_src_dir)" \
+	-name "*.[chS]" -print > cscope.files
+	$(CMD_PREFIX)echo "$(KCONFIG_AUTOHEADER)" >> cscope.files
+	$(CMD_PREFIX)cscope -bkq -i cscope.files -f cscope.out
+
 # Rule for "make clean"
 .PHONY: clean
 clean:
@@ -606,6 +670,8 @@ ifeq ($(install_root_dir),$(install_root_dir_default)/usr)
 	$(if $(V), @echo " RM        $(install_root_dir_default)")
 	$(CMD_PREFIX)rm -rf $(install_root_dir_default)
 endif
+	$(if $(V), @echo " RM        $(src_dir)/cscope*")
+	$(CMD_PREFIX)rm -f $(src_dir)/cscope*
 
 .PHONY: FORCE
 FORCE:
diff --git a/README.md b/README.md
index 54fabe4..895bbf2 100644
--- a/README.md
+++ b/README.md
@@ -92,8 +92,8 @@ N.B. Any S-mode boot loader (i.e. U-Boot) doesn't need to support HSM extension,
 as it doesn't need to boot all the harts. The operating system should be
 capable enough to bring up all other non-booting harts using HSM extension.
 
-Required Toolchain
-------------------
+Required Toolchain and Packages
+-------------------------------
 
 OpenSBI can be compiled natively or cross-compiled on a x86 host. For
 cross-compilation, you can build your own toolchain, download a prebuilt one
@@ -115,6 +115,14 @@ triple is used (e.g. *-target riscv64-unknown-elf*).
 Please note that only a 64-bit version of the toolchain is available in
 the Bootlin toolchain repository for now.
 
+In addition to a toolchain, OpenSBI also requires the following packages on
+the host:
+
+1. device-tree-compiler: The device tree compiler for compiling device
+   tree sources (DTS files).
+2. python3: The python 3.0 (or compatible) language support for various
+   scripts.
+
 Building and Installing the OpenSBI Platform-Independent Library
 ----------------------------------------------------------------
 
@@ -196,6 +204,19 @@ top-level make command line. These options, such as *PLATFORM_<xyz>* or
 *docs/platform/<platform_name>.md* files and
 *docs/firmware/<firmware_name>.md* files.
 
+All OpenSBI platforms support Kconfig style build-time configuration. Users
+can change the build-time configuration of a platform using a graphical
+interface as follows:
+```
+make PLATFORM=<platform_subdir> menuconfig
+```
+
+Alternately, an OpenSBI platform can have multiple default configurations
+and users can select a custom default configuration as follows:
+```
+make PLATFORM=<platform_subdir> PLATFORM_DEFCONFIG=<platform_custom_defconfig>
+```
+
 Building 32-bit / 64-bit OpenSBI Images
 ---------------------------------------
 By default, building OpenSBI generates 32-bit or 64-bit images based on the
diff --git a/debian/changelog b/debian/changelog
index c4d673a..fa23b1e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+opensbi (1.2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * Drop patch lib-sbi_illegal_insn-Fix-FENCE.TSO-emulation-infinit.patch,
+    present upstream.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 26 Feb 2023 10:27:37 -0000
+
 opensbi (1.1-2) unstable; urgency=medium
 
   [ Lintian Brush ]
diff --git a/debian/patches/lib-sbi_illegal_insn-Fix-FENCE.TSO-emulation-infinit.patch b/debian/patches/lib-sbi_illegal_insn-Fix-FENCE.TSO-emulation-infinit.patch
deleted file mode 100644
index 2f93f9a..0000000
--- a/debian/patches/lib-sbi_illegal_insn-Fix-FENCE.TSO-emulation-infinit.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 111afc12306e4368fa112f97d31ea802c4561294 Mon Sep 17 00:00:00 2001
-From: Rahul Pathak <rpathak@ventanamicro.com>
-Date: Fri, 12 Aug 2022 19:24:42 +0530
-Subject: [PATCH] lib: sbi_illegal_insn: Fix FENCE.TSO emulation infinite trap
- loop
-
-In case of missing "FENCE.TSO" instruction implementation,
-opensbi can emulate the "FENCE.TSO" with "FENCE RW,RW", but
-mepc was not incremented to continue from the next instruction
-causing infinite trap.
-
-Fixes: cb8271c8 ("lib: sbi_illegal_insn: Add emulation for fence.tso")
-Signed-off-by: Rahul Pathak <rpathak@ventanamicro.com>
-Reviewed-by: Andrew Jones <ajones@ventanamicro.com>
-Reviewed-by: Xiang W <wxjstz@126.com>
-Reviewed-by: Samuel Holland <samuel@sholland.org>
-Origin: https://github.com/riscv-software-src/opensbi/commit/111afc12306e4368fa112f97d31ea802c4561294
----
- lib/sbi/sbi_illegal_insn.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/lib/sbi/sbi_illegal_insn.c b/lib/sbi/sbi_illegal_insn.c
-index ecd3508..9691bce 100644
---- a/lib/sbi/sbi_illegal_insn.c
-+++ b/lib/sbi/sbi_illegal_insn.c
-@@ -40,6 +40,7 @@ static int misc_mem_opcode_insn(ulong insn, struct sbi_trap_regs *regs)
- 	/* Errata workaround: emulate `fence.tso` as `fence rw, rw`. */
- 	if ((insn & INSN_MASK_FENCE_TSO) == INSN_MATCH_FENCE_TSO) {
- 		smp_mb();
-+		regs->mepc += 4;
- 		return 0;
- 	}
- 
--- 
-2.37.2
-
diff --git a/debian/patches/series b/debian/patches/series
index 7419347..e69de29 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +0,0 @@
-lib-sbi_illegal_insn-Fix-FENCE.TSO-emulation-infinit.patch
diff --git a/docs/contributing.md b/docs/contributing.md
index 9c2b3ba..78d909e 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -29,7 +29,7 @@ and "top:".
 5. Maintainers should use "Rebase and Merge" when using GitHub to merge pull
 requests to avoid creating unnecessary merge commits.
 6. Maintainers should avoid creating branches directly in the main
-riscv/opensbi repository. Instead prefer using a fork of the riscv/opensbi main
+riscv/opensbi repository. Instead, prefer using a fork of the riscv/opensbi main
 repository and branches within that fork to create pull requests.
 7. A maintainer cannot merge his own pull requests in the riscv/opensbi main
 repository.
diff --git a/docs/domain_support.md b/docs/domain_support.md
index 73931f1..8963b57 100644
--- a/docs/domain_support.md
+++ b/docs/domain_support.md
@@ -2,7 +2,7 @@ OpenSBI Domain Support
 ======================
 
 An OpenSBI domain is a system-level partition (subset) of underlying hardware
-having it's own memory regions (RAM and MMIO devices) and HARTs. The OpenSBI
+having its own memory regions (RAM and MMIO devices) and HARTs. The OpenSBI
 will try to achieve secure isolation between domains using RISC-V platform
 features such as PMP, ePMP, IOPMP, SiFive Shield, etc.
 
@@ -15,7 +15,7 @@ Important entities which help implement OpenSBI domain support are:
 Each HART of a RISC-V platform must have an OpenSBI domain assigned to it.
 The OpenSBI platform support is responsible for populating domains and
 providing HART id to domain mapping. The OpenSBI domain support will by
-default assign **the ROOT domain** to all HARTs of a RISC-V platform so
+default assign **the ROOT domain** to all HARTs of a RISC-V platform, so
 it is not mandatory for the OpenSBI platform support to populate domains.
 
 Domain Memory Region
@@ -29,7 +29,7 @@ OpenSBI and has following details:
 * **base** - The base address of a memory region is **2 ^ order**
   aligned start address
 * **flags** - The flags of a memory region represent memory type (i.e.
-  RAM or MMIO) and allowed accesses (i.e. READ, WRITE, EXECUTE, etc)
+  RAM or MMIO) and allowed accesses (i.e. READ, WRITE, EXECUTE, etc.)
 
 Domain Instance
 ---------------
diff --git a/docs/firmware/fw.md b/docs/firmware/fw.md
index 5a67848..18faaa9 100644
--- a/docs/firmware/fw.md
+++ b/docs/firmware/fw.md
@@ -53,7 +53,7 @@ the booting stage to follow OpenSBI firmware.
 A *FW_PAYLOAD* firmware is also useful for cases where the booting stage prior
 to OpenSBI firmware does not pass a *flattened device tree (FDT file)*. In such
 case, a *FW_PAYLOAD* firmware allows embedding a flattened device tree in the
-.text section of the final firmware.
+.rodata section of the final firmware.
 
 Firmware Configuration and Compilation
 --------------------------------------
diff --git a/docs/firmware/fw_dynamic.md b/docs/firmware/fw_dynamic.md
index 7b9b192..9fc8cfa 100644
--- a/docs/firmware/fw_dynamic.md
+++ b/docs/firmware/fw_dynamic.md
@@ -20,7 +20,7 @@ the booting stage binary to follow OpenSBI firmware.
 A platform can enable *FW_DYNAMIC* firmware using any of the following methods.
 
 1. Specifying `FW_DYNAMIC=y` on the top level `make` command line.
-2. Specifying `FW_DYNAMIC=y` in the target platform *config.mk* configuration
+2. Specifying `FW_DYNAMIC=y` in the target platform *objects.mk* configuration
 file.
 
 The compiled *FW_DYNAMIC* firmware ELF file is named *fw_dynamic.elf*. It's
@@ -31,6 +31,6 @@ directory.
 *FW_DYNAMIC* Firmware Configuration Options
 -------------------------------------------
 
-The *FW_DYNAMIC* firmware does not requires any platform specific configuration
+The *FW_DYNAMIC* firmware does not require any platform specific configuration
 parameters because all required information is passed by previous booting stage
 at runtime via *struct fw_dynamic_info*.
diff --git a/docs/firmware/fw_jump.md b/docs/firmware/fw_jump.md
index eea3013..956897e 100644
--- a/docs/firmware/fw_jump.md
+++ b/docs/firmware/fw_jump.md
@@ -15,7 +15,7 @@ and the booting stage binary to follow the OpenSBI firmware.
 A platform *FW_JUMP* firmware can be enabled by any of the following methods:
 
 1. Specifying `FW_JUMP=y` on the top level `make` command line.
-2. Specifying `FW_JUMP=y` in the target platform *config.mk* configuration file.
+2. Specifying `FW_JUMP=y` in the target platform *objects.mk* configuration file.
 
 The compiled *FW_JUMP* firmware ELF file is named *fw_jump.elf*. Its expanded
 image file is *fw_jump.bin*. Both files are created in the platform-specific
@@ -26,7 +26,7 @@ build directory under the *build/platform/<platform_subdir>/firmware* directory.
 
 To operate correctly, a *FW_JUMP* firmware requires some configuration
 parameters to be defined using either the top level `make` command line or the
-target platform *config.mk* configuration file. The possible parameters are as
+target platform *objects.mk* configuration file. The possible parameters are as
 follows:
 
 * **FW_JUMP_ADDR** - Address of the entry point of the booting stage to be
@@ -41,6 +41,22 @@ follows:
   provided, then the OpenSBI firmware will pass the FDT address passed by the
   previous booting stage to the next booting stage.
 
+  When using the default *FW_JUMP_FDT_ADDR* with *PLATFORM=generic*, you must
+  ensure *FW_JUMP_FDT_ADDR* is set high enough to avoid overwriting the kernel.
+  You can use the following method.
+
+  ```
+  ${CROSS_COMPILE}objdump -h $KERNEL_ELF | sort -k 5,5 | awk -n '/^ +[0-9]+ /\
+  {addr="0x"$3; size="0x"$5; printf "0x""%x\n",addr+size}' \
+  | (( `tail -1` > 0x2200000 )) && echo fdt overlaps kernel,\
+  increase FW_JUMP_FDT_ADDR
+
+  ${LLVM}objdump -h --show-lma $KERNEL_ELF | sort -k 5,5 | \
+  awk -n '/^ +[0-9]+ / {addr="0x"$3; size="0x"$5; printf "0x""%x\n",addr+size}'\
+  | (( `tail -1` > 0x2200000 )) && echo fdt overlaps kernel,\
+  increase FW_JUMP_FDT_ADDR
+  ```
+
 *FW_JUMP* Example
 -----------------
 
diff --git a/docs/firmware/fw_payload.md b/docs/firmware/fw_payload.md
index 0947448..3d50ddc 100644
--- a/docs/firmware/fw_payload.md
+++ b/docs/firmware/fw_payload.md
@@ -12,7 +12,7 @@ firmware and the booting stage to follow OpenSBI firmware.
 A *FW_PAYLOAD* firmware is also useful for cases where the booting stage prior
 to the OpenSBI firmware does not pass a *flattened device tree (FDT file)*. In
 such a case, a *FW_PAYLOAD* firmware allows embedding a flattened device tree
-in the .text section of the final firmware.
+in the .rodata section of the final firmware.
 
 Enabling *FW_PAYLOAD* compilation
 ---------------------------------
@@ -20,7 +20,7 @@ Enabling *FW_PAYLOAD* compilation
 The *FW_PAYLOAD* firmware can be enabled by any of the following methods:
 
 1. Specifying `FW_PAYLOAD=y` on the top level `make` command line.
-2. Specifying `FW_PAYLOAD=y` in the target platform *config.mk* configuration
+2. Specifying `FW_PAYLOAD=y` in the target platform *objects.mk* configuration
    file.
 
 The compiled *FW_PAYLOAD* firmware ELF file is named *fw_jump.elf*. Its
@@ -33,7 +33,7 @@ Configuration Options
 
 A *FW_PAYLOAD* firmware is built according to configuration parameters and
 options. These configuration parameters can be defined using either the top
-level `make` command line or the target platform *config.mk* configuration
+level `make` command line or the target platform *objects.mk* configuration
 file. The parameters currently defined are as follows:
 
 * **FW_PAYLOAD_OFFSET** - Offset from *FW_TEXT_BASE* where the payload binary
diff --git a/docs/library_usage.md b/docs/library_usage.md
index ff99801..bb64bc4 100644
--- a/docs/library_usage.md
+++ b/docs/library_usage.md
@@ -8,11 +8,7 @@ OpenSBI provides two types of static libraries:
    hooks for the execution of this interface must be provided by the firmware or
    bootloader linking with this library. This library is installed as
    *<install_directory>/lib/libsbi.a*
-2. *libsbiutils.a* - A static library that will contain all common code required
-   by any platform supported in OpenSBI. It will be built by default and included
-   in libplatsbi.a. This library is installed as
-   *<install_directory>/lib/libsbiutils.a*.
-3. *libplatsbi.a* - An example platform-specific static library integrating
+2. *libplatsbi.a* - An example platform-specific static library integrating
    *libsbi.a* with platform-specific hooks. This library is available only for
    the platforms supported by OpenSBI. This library is installed as
    *<install_directory>/platform/<platform_subdir>/lib/libplatsbi.a*
@@ -77,7 +73,7 @@ firmware drivers based on the external firmware architecture.
 **OPENSBI_EXTERNAL_SBI_TYPES** identifier is introduced to *sbi_types.h* for selecting
 external header file during the build preprocess in order to define OpensSBI data types
 based on external firmware data type binding.
-For example, *bool* is declared as *int* in sbi_types.h. However in EDK2 build system,
+For example, *bool* is declared as *int* in sbi_types.h. However, in EDK2 build system,
 *bool* is declared as *BOOLEAN* which is defined as *unsigned char* data type.
 
 External firmware can define **OPENSBI_EXTERNAL_SBI_TYPES** in CFLAGS and specify it to the
diff --git a/docs/platform/andes-ae350.md b/docs/platform/andes-ae350.md
index 1cf83cf..8fc8ec6 100644
--- a/docs/platform/andes-ae350.md
+++ b/docs/platform/andes-ae350.md
@@ -8,7 +8,7 @@ AHB/APB IPs suites a majority embedded systems, and the verified platform serves
 as a starting point to jump start SoC designs.
 
 To build platform specific library and firmwares, provide the
-*PLATFORM=andes/ae350* parameter to the top level make command.
+*PLATFORM=generic* parameter to the top level `make` command.
 
 Platform Options
 ----------------
@@ -18,13 +18,190 @@ The Andes AE350 platform does not have any platform-specific options.
 Building Andes AE350 Platform
 -----------------------------
 
-To use Linux v5.2 should be used to build Andes AE350 OpenSBI binaries by using
-the compile time option FW_FDT_PATH.
-
-AE350's dts is included in https://github.com/andestech/linux/tree/ast-v3_2_0-release-public
+AE350's dts is included in https://github.com/andestech/linux/tree/RISCV-Linux-5.4-ast-v5_1_0-branch
 
 **Linux Kernel Payload**
 
 ```
-make PLATFORM=andes/ae350 FW_PAYLOAD_PATH=<linux_build_directory>/arch/riscv/boot/Image FW_FDT_PATH=<ae350.dtb path>
+make PLATFORM=generic FW_PAYLOAD_PATH=<linux_build_directory>/arch/riscv/boot/Image FW_FDT_PATH=<ae350.dtb path>
+```
+
+DTS Example: (Quad-core AX45MP)
+-------------------------------
+
+```
+	compatible = "andestech,ae350";
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		timebase-frequency = <60000000>;
+
+		CPU0: cpu@0 {
+			device_type = "cpu";
+			reg = <0>;
+			status = "okay";
+			compatible = "riscv";
+			riscv,isa = "rv64imafdc";
+			riscv,priv-major = <1>;
+			riscv,priv-minor = <10>;
+			mmu-type = "riscv,sv48";
+			clock-frequency = <60000000>;
+			i-cache-size = <0x8000>;
+			i-cache-sets = <256>;
+			i-cache-line-size = <64>;
+			i-cache-block-size = <64>;
+			d-cache-size = <0x8000>;
+			d-cache-sets = <128>;
+			d-cache-line-size = <64>;
+			d-cache-block-size = <64>;
+			next-level-cache = <&L2>;
+			CPU0_intc: interrupt-controller {
+				#interrupt-cells = <1>;
+				interrupt-controller;
+				compatible = "riscv,cpu-intc";
+			};
+		};
+		CPU1: cpu@1 {
+			device_type = "cpu";
+			reg = <1>;
+			status = "okay";
+			compatible = "riscv";
+			riscv,isa = "rv64imafdc";
+			riscv,priv-major = <1>;
+			riscv,priv-minor = <10>;
+			mmu-type = "riscv,sv48";
+			clock-frequency = <60000000>;
+			i-cache-size = <0x8000>;
+			i-cache-sets = <256>;
+			i-cache-line-size = <64>;
+			i-cache-block-size = <64>;
+			d-cache-size = <0x8000>;
+			d-cache-sets = <128>;
+			d-cache-line-size = <64>;
+			d-cache-block-size = <64>;
+			next-level-cache = <&L2>;
+			CPU1_intc: interrupt-controller {
+				#interrupt-cells = <1>;
+				interrupt-controller;
+				compatible = "riscv,cpu-intc";
+			};
+		};
+		CPU2: cpu@2 {
+			device_type = "cpu";
+			reg = <2>;
+			status = "okay";
+			compatible = "riscv";
+			riscv,isa = "rv64imafdc";
+			riscv,priv-major = <1>;
+			riscv,priv-minor = <10>;
+			mmu-type = "riscv,sv48";
+			clock-frequency = <60000000>;
+			i-cache-size = <0x8000>;
+			i-cache-sets = <256>;
+			i-cache-line-size = <64>;
+			i-cache-block-size = <64>;
+			d-cache-size = <0x8000>;
+			d-cache-sets = <128>;
+			d-cache-line-size = <64>;
+			d-cache-block-size = <64>;
+			next-level-cache = <&L2>;
+			CPU2_intc: interrupt-controller {
+				#interrupt-cells = <1>;
+				interrupt-controller;
+				compatible = "riscv,cpu-intc";
+			};
+		};
+		CPU3: cpu@3 {
+			device_type = "cpu";
+			reg = <3>;
+			status = "okay";
+			compatible = "riscv";
+			riscv,isa = "rv64imafdc";
+			riscv,priv-major = <1>;
+			riscv,priv-minor = <10>;
+			mmu-type = "riscv,sv48";
+			clock-frequency = <60000000>;
+			i-cache-size = <0x8000>;
+			i-cache-sets = <256>;
+			i-cache-line-size = <64>;
+			i-cache-block-size = <64>;
+			d-cache-size = <0x8000>;
+			d-cache-sets = <128>;
+			d-cache-line-size = <64>;
+			d-cache-block-size = <64>;
+			next-level-cache = <&L2>;
+			CPU3_intc: interrupt-controller {
+				#interrupt-cells = <1>;
+				interrupt-controller;
+				compatible = "riscv,cpu-intc";
+			};
+		};
+	};
+
+	soc {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		compatible = "andestech,riscv-ae350-soc", "simple-bus";
+		ranges;
+
+		plic0: interrupt-controller@e4000000 {
+			compatible = "riscv,plic0";
+			reg = <0x00000000 0xe4000000 0x00000000 0x02000000>;
+			interrupts-extended = < &CPU0_intc 11 &CPU0_intc 9
+			                        &CPU1_intc 11 &CPU1_intc 9
+			                        &CPU2_intc 11 &CPU2_intc 9
+			                        &CPU3_intc 11 &CPU3_intc 9 >;
+			interrupt-controller;
+			#address-cells = <2>;
+			#interrupt-cells = <2>;
+			riscv,ndev = <71>;
+		};
+
+		plicsw: interrupt-controller@e6400000 {
+			compatible = "andestech,plicsw";
+			reg = <0x00000000 0xe6400000 0x00000000 0x00400000>;
+			interrupts-extended = < &CPU0_intc 3
+			                        &CPU1_intc 3
+			                        &CPU2_intc 3
+			                        &CPU3_intc 3 >;
+			interrupt-controller;
+			#address-cells = <2>;
+			#interrupt-cells = <2>;
+		};
+
+		plmt0: plmt0@e6000000 {
+			compatible = "andestech,plmt0";
+			reg = <0x00000000 0xe6000000 0x00000000 0x00100000>;
+			interrupts-extended = < &CPU0_intc 7
+			                        &CPU1_intc 7
+			                        &CPU2_intc 7
+			                        &CPU3_intc 7 >;
+		};
+
+		wdt: watchdog@f0500000 {
+			compatible = "andestech,atcwdt200";
+			reg = <0x00000000 0xf0500000 0x00000000 0x00001000>;
+			interrupts = <3 4>;
+			interrupt-parent = <&plic0>;
+			clock-frequency = <15000000>;
+		};
+
+		serial0: serial@f0300000 {
+			compatible = "andestech,uart16550", "ns16550a";
+			reg = <0x00000000 0xf0300000 0x00000000 0x00001000>;
+			interrupts = <9 4>;
+			interrupt-parent = <&plic0>;
+			clock-frequency = <19660800>;
+			current-speed = <38400>;
+			reg-shift = <2>;
+			reg-offset = <32>;
+			reg-io-width = <4>;
+			no-loopback-test = <1>;
+		};
+
+		smu: smu@f0100000 {
+			compatible = "andestech,atcsmu";
+			reg = <0x00000000 0xf0100000 0x00000000 0x00001000>;
+		};
+	};
 ```
diff --git a/docs/platform/fpga-ariane.md b/docs/platform/fpga-ariane.md
index f95001f..9b1e156 100644
--- a/docs/platform/fpga-ariane.md
+++ b/docs/platform/fpga-ariane.md
@@ -7,7 +7,7 @@ Linux.
 
 The FPGA SoC currently contains the following peripherals:
 - DDR3 memory controller
-- SPI controller to conncet to an SDCard
+- SPI controller to connect to an SDCard
 - Ethernet controller
 - JTAG port (see debugging section below)
 - Bootrom containing zero stage bootloader and device tree.
diff --git a/docs/platform/generic.md b/docs/platform/generic.md
index 73a8afa..8e4cf2d 100644
--- a/docs/platform/generic.md
+++ b/docs/platform/generic.md
@@ -45,13 +45,17 @@ The *Generic* platform does not have any platform-specific options.
 RISC-V Platforms Using Generic Platform
 ---------------------------------------
 
+* **Andes AE350 Platform** (*[andes-ae350.md]*)
 * **QEMU RISC-V Virt Machine** (*[qemu_virt.md]*)
+* **Renesas RZ/Five SoC** (*[renesas-rzfive.md]*)
 * **Shakti C-class SoC Platform** (*[shakti_cclass.md]*)
 * **SiFive HiFive Unleashed** (*[sifive_fu540.md]*)
 * **Spike** (*[spike.md]*)
 * **T-HEAD C9xx series Processors** (*[thead-c9xx.md]*)
 
+[andes-ae350.md]: andse-ae350.md
 [qemu_virt.md]: qemu_virt.md
+[renesas-rzfive.md]: renesas-rzfive.md
 [shakti_cclass.md]: shakti_cclass.md
 [sifive_fu540.md]: sifive_fu540.md
 [spike.md]: spike.md
diff --git a/docs/platform/platform.md b/docs/platform/platform.md
index cb4bec6..4504d87 100644
--- a/docs/platform/platform.md
+++ b/docs/platform/platform.md
@@ -39,11 +39,15 @@ OpenSBI currently supports the following virtual and hardware platforms:
   processor based SOCs. More details on this platform can be found in the
   file *[shakti_cclass.md]*.
 
+* **Renesas RZ/Five SoC**: Platform support for Renesas RZ/Five (R9A07G043F) SoC
+  used on the Renesas RZ/Five SMARC EVK board. More details on this platform can
+  be found in the file *[renesas-rzfive.md]*.
+
 The code for these supported platforms can be used as example to implement
 support for other platforms. The *platform/template* directory also provides
-template files for implementing support for a new platform. The *object.mk*,
-*config.mk* and *platform.c* template files provides enough comments to
-facilitate the implementation.
+template files for implementing support for a new platform. The *objects.mk*,
+*Kconfig*, *configs/defconfig* and *platform.c* template files provides enough
+comments to facilitate the implementation.
 
 [generic.md]: generic.md
 [qemu_virt.md]: qemu_virt.md
@@ -54,3 +58,4 @@ facilitate the implementation.
 [spike.md]: spike.md
 [fpga-openpiton.md]: fpga-openpiton.md
 [shakti_cclass.md]: shakti_cclass.md
+[renesas-rzfive.md]: renesas-rzfive.md
diff --git a/docs/platform/renesas-rzfive.md b/docs/platform/renesas-rzfive.md
new file mode 100644
index 0000000..a73c53f
--- /dev/null
+++ b/docs/platform/renesas-rzfive.md
@@ -0,0 +1,160 @@
+Renesas RZ/Five SoC (R9A07G043F) Platform
+=========================================
+The RZ/Five microprocessor includes a single RISC-V CPU Core (Andes AX45MP)
+1.0 GHz, 16-bit DDR3L/DDR4 interface. Supported interfaces include:
+- Memory controller for DDR4-1600 / DDR3L-1333 with 16 bits
+- System RAM (RAM of 128 Kbytes (ECC))
+- SPI Multi I/O Bus Controller 1ch
+- SD Card Host Interface/Multimedia Card Interface (SD/MMC) 2ch
+- Serial Sound Interface (SSI) 4ch
+- Sampling Rate Converter (SRC) 1ch
+- USB2.0 host/function interface 2ch (ch0: Host-Function ch1: Host only)
+- Gigabit Ethernet Interface (GbE) 2ch
+- Controller Area Network Interface (CAN) 2ch (CAN-FD ISO 11898-1 (CD2014) compliant)
+- Multi-function Timer Pulse Unit 3 (MTU3a) 9 ch (16 bits × 8 channels, 32 bits × 1 channel)
+- Port Output Enable 3 (POE3)
+- Watchdog Timer (WDT) 1ch
+- General Timer (GTM) 3ch (32bits)
+- I2C Bus Interface (I2C) 4ch
+- Serial Communication Interface with FIFO (SCIFA) 5ch
+- Serial Communication Interface (SCI) 2ch
+- Renesas Serial Peripheral Interface (RSPI) 3ch
+- A/D Converter (ADC) 2ch
+making it ideal for applications such as entry-class social infrastructure
+gateway control and industrial gateway control. More details can be found at
+below link [0].
+
+[0] https://www.renesas.com/us/en/products/microcontrollers-microprocessors/rz-mpus/rzfive-general-purpose-microprocessors-risc-v-cpu-core-andes-ax45mp-single-10-ghz-2ch-gigabit-ethernet
+
+To build platform specific library and firmwares, provide the
+*PLATFORM=generic* parameter to the top level make command.
+
+Platform Options
+----------------
+
+The Renesas RZ/Five platform does not have any platform-specific options.
+
+Building Renesas RZ/Five Platform
+---------------------------------
+
+```
+make PLATFORM=generic
+```
+
+DTS Example: (RZ/Five AX45MP)
+-----------------------------
+
+```
+	compatible = "renesas,r9a07g043f01", "renesas,r9a07g043";
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		timebase-frequency = <12000000>;
+
+		cpu0: cpu@0 {
+			compatible = "andestech,ax45mp", "riscv";
+			device_type = "cpu";
+			reg = <0x0>;
+			status = "okay";
+			riscv,isa = "rv64imafdc";
+			mmu-type = "riscv,sv39";
+			i-cache-size = <0x8000>;
+			i-cache-line-size = <0x40>;
+			d-cache-size = <0x8000>;
+			d-cache-line-size = <0x40>;
+			clocks = <&cpg CPG_CORE R9A07G043_CLK_I>;
+
+			cpu0_intc: interrupt-controller {
+				#interrupt-cells = <1>;
+				compatible = "riscv,cpu-intc";
+				interrupt-controller;
+			};
+		};
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		ranges;
+
+		scif0: serial@1004b800 {
+			compatible = "renesas,scif-r9a07g043",
+				     "renesas,scif-r9a07g044";
+			reg = <0 0x1004b800 0 0x400>;
+			interrupts = <412 IRQ_TYPE_LEVEL_HIGH>,
+				     <414 IRQ_TYPE_LEVEL_HIGH>,
+				     <415 IRQ_TYPE_LEVEL_HIGH>,
+				     <413 IRQ_TYPE_LEVEL_HIGH>,
+				     <416 IRQ_TYPE_LEVEL_HIGH>,
+				     <416 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "eri", "rxi", "txi",
+					  "bri", "dri", "tei";
+			clocks = <&cpg CPG_MOD R9A07G043_SCIF0_CLK_PCK>;
+			clock-names = "fck";
+			power-domains = <&cpg>;
+			resets = <&cpg R9A07G043_SCIF0_RST_SYSTEM_N>;
+			status = "disabled";
+		};
+
+		cpg: clock-controller@11010000 {
+			compatible = "renesas,r9a07g043-cpg";
+			reg = <0 0x11010000 0 0x10000>;
+			clocks = <&extal_clk>;
+			clock-names = "extal";
+			#clock-cells = <2>;
+			#reset-cells = <1>;
+			#power-domain-cells = <0>;
+		};
+
+		sysc: system-controller@11020000 {
+			compatible = "renesas,r9a07g043-sysc";
+			reg = <0 0x11020000 0 0x10000>;
+			status = "disabled";
+		};
+
+		pinctrl: pinctrl@11030000 {
+			compatible = "renesas,r9a07g043-pinctrl";
+			reg = <0 0x11030000 0 0x10000>;
+			gpio-controller;
+			#gpio-cells = <2>;
+			#interrupt-cells = <2>;
+			interrupt-controller;
+			gpio-ranges = <&pinctrl 0 0 152>;
+			clocks = <&cpg CPG_MOD R9A07G043_GPIO_HCLK>;
+			power-domains = <&cpg>;
+			resets = <&cpg R9A07G043_GPIO_RSTN>,
+				 <&cpg R9A07G043_GPIO_PORT_RESETN>,
+				 <&cpg R9A07G043_GPIO_SPARE_RESETN>;
+		};
+
+		plmt0: plmt0@110c0000 {
+			compatible = "andestech,plmt0", "riscv,plmt0";
+			reg = <0x0 0x110c0000 0x0 0x10000>;
+			interrupts-extended = <&cpu0_intc 7>;
+		};
+
+		plic: interrupt-controller@12c00000 {
+			compatible = "renesas,r9a07g043-plic", "andestech,nceplic100";
+			#interrupt-cells = <2>;
+			#address-cells = <0>;
+			riscv,ndev = <511>;
+			interrupt-controller;
+			reg = <0x0 0x12c00000 0x0 0x400000>;
+			clocks = <&cpg CPG_MOD R9A07G043_NCEPLIC_ACLK>;
+			power-domains = <&cpg>;
+			resets = <&cpg R9A07G043_NCEPLIC_ARESETN>;
+			interrupts-extended = <&cpu0_intc 11 &cpu0_intc 9>;
+		};
+
+		plicsw: interrupt-controller@13000000 {
+			compatible = "andestech,plicsw";
+			reg = <0x0 0x13000000 0x0 0x400000>;
+			interrupts-extended = <&cpu0_intc 3>;
+			interrupt-controller;
+			#address-cells = <2>;
+			#interrupt-cells = <2>;
+		};
+	};
+```
diff --git a/docs/platform/sifive_fu540.md b/docs/platform/sifive_fu540.md
index 5cbca21..3dc1d28 100644
--- a/docs/platform/sifive_fu540.md
+++ b/docs/platform/sifive_fu540.md
@@ -150,7 +150,7 @@ If you want to test OpenSBI with QEMU 'sifive_u' machine, please follow the
 same instructions above, with the exception of not passing FW_FDT_PATH.
 
 This is because QEMU generates a device tree blob on the fly based on the
-command line parameters and it's compatible with the one used in the upstream
+command line parameters, and it's compatible with the one used in the upstream
 Linux kernel.
 
 When U-Boot v2021.07 (or higher) is used as the payload, as the SiFive FU540
diff --git a/docs/platform/thead-c9xx.md b/docs/platform/thead-c9xx.md
index ced784d..8bb9e91 100644
--- a/docs/platform/thead-c9xx.md
+++ b/docs/platform/thead-c9xx.md
@@ -13,7 +13,7 @@ Platform Options
 ----------------
 
 The *T-HEAD C9xx* does not have any platform-specific compile options
-because it use generic platform.
+because it uses generic platform.
 
 ```
 CROSS_COMPILE=riscv64-linux-gnu- PLATFORM=generic /usr/bin/make
diff --git a/docs/platform_guide.md b/docs/platform_guide.md
index b6c2c2e..38fd516 100644
--- a/docs/platform_guide.md
+++ b/docs/platform_guide.md
@@ -28,11 +28,12 @@ Adding support for a new platform
 Support for a new platform named *&lt;xyz&gt;* can be added as follows:
 
 1. Create a directory named *&lt;xyz&gt;* under the *platform/* directory.
-2. Create a platform configuration file named *config.mk* under the
-   *platform/&lt;xyz&gt;/* directory. This configuration file will provide
+2. Create platform configuration files named *Kconfig* and *configs/defconfig*
+   under the *platform/&lt;xyz&gt;/* directory. These configuration files will
+   provide the build time configuration for the sources to be compiled.
+3. Create a *platform/&lt;xyz&gt;/objects.mk* file for listing the platform
+   object files to be compiled. This file also provides platform-specific
    compiler flags, and select firmware options.
-3. Create a *platform/&lt;xyz&gt;/objects.mk* file for listing the
-   platform-specific object files to be compiled.
 4. Create a *platform/&lt;xyz&gt;/platform.c* file providing a
    *struct sbi_platform* instance.
 
diff --git a/docs/platform_requirements.md b/docs/platform_requirements.md
index 68dc393..8735adb 100644
--- a/docs/platform_requirements.md
+++ b/docs/platform_requirements.md
@@ -10,7 +10,7 @@ To handle this, we have two types of RISC-V platform requirements:
 2. **Release specific platform requirements** which apply to a OpenSBI
    release and later releases
 
-Currently, we don't have any **Release specific platform requirements**
+Currently, we don't have any **Release specific platform requirements**,
 but such platform requirements will be added in future.
 
 Base Platform Requirements
diff --git a/docs/pmu_support.md b/docs/pmu_support.md
index 8751bb5..8cfa08c 100644
--- a/docs/pmu_support.md
+++ b/docs/pmu_support.md
@@ -1,14 +1,11 @@
 OpenSBI SBI PMU extension support
 ==================================
 SBI PMU extension supports allow supervisor software to configure/start/stop
-any performance counter at anytime. Thus, an user can leverage full
+any performance counter at anytime. Thus, a user can leverage full
 capability of performance analysis tools such as perf if SBI PMU extension is
 enabled. The OpenSBI implementation makes the following assumptions about the
 hardware platform.
 
- * MCOUNTINHIBIT CSR must be implemented in the hardware. Otherwise, SBI PMU
-extension will not be enabled.
-
  * The platform must provide information about PMU event to counter mapping
 via device tree or platform specific hooks. Otherwise, SBI PMU extension will
 not be enabled.
@@ -25,7 +22,7 @@ SBI PMU Device Tree Bindings
 ----------------------------
 
 Platforms may choose to describe PMU event selector and event to counter mapping
-values via device tree. The following sections describes the PMU DT node
+values via device tree. The following sections describe the PMU DT node
 bindings in details.
 
 * **compatible** (Mandatory) - The compatible string of SBI PMU device tree node.
@@ -42,21 +39,21 @@ This property shouldn't encode any raw hardware event.
 * **riscv,event-to-mhpmcounters**(Optional) - It represents a MANY-to-MANY
 mapping between a range of events and all the MHPMCOUNTERx in a bitmap format
 that can be used to monitor these range of events. The information is encoded in
-a table format where each row represent a certain range of events and
+a table format where each row represents a certain range of events and
 corresponding counters. The first column represents starting of the pmu event id
 and 2nd column represents the end of the pmu event id. The third column
 represent a bitmap of all the MHPMCOUNTERx. This property is mandatory if
-event-to-mhpmevent is present. Otherwise, it can be omitted. This property
+riscv,event-to-mhpmevent is present. Otherwise, it can be omitted. This property
 shouldn't encode any raw event.
 
 * **riscv,raw-event-to-mhpmcounters**(Optional) - It represents an ONE-to-MANY
 or MANY-to-MANY mapping between the raw event(s) and all the MHPMCOUNTERx in
 a bitmap format that can be used to monitor that raw event. The encoding of the
 raw events are platform specific. The information is encoded in a table format
-where each row represent the specific raw event(s). The first column is a 64bit
+where each row represents the specific raw event(s). The first column is a 64bit
 match value where the invariant bits of range of events are set. The second
 column is a 64 bit mask that will have all the variant bits of the range of
-events cleared. Every other bits should be set in the mask.
+events cleared. All other bits should be set in the mask.
 The third column is a 32bit value to represent bitmap of all MHPMCOUNTERx that
 can monitor these set of event(s).
 If a platform directly encodes each raw PMU event as a unique ID, the value of
@@ -70,17 +67,17 @@ via platform hooks rather than the device tree.
 ```
 pmu {
 	compatible 			= "riscv,pmu";
-	riscv,event-to-mhpmevent 		= <0x0000B  0x0000 0x0001>,
+	riscv,event-to-mhpmevent 		= <0x0000B  0x0000 0x0001>;
 	riscv,event-to-mhpmcounters 	= <0x00001 0x00001 0x00000001>,
 						  <0x00002 0x00002 0x00000004>,
 						  <0x00003 0x0000A 0x00000ff8>,
-						  <0x10000 0x10033 0x000ff000>,
+						  <0x10000 0x10033 0x000ff000>;
 					/* For event ID 0x0002 */
 	riscv,raw-event-to-mhpmcounters = <0x0000 0x0002 0xffffffff 0xffffffff 0x00000f8>,
 					/* For event ID 0-4 */
 					<0x0 0x0 0xffffffff 0xfffffff0 0x00000ff0>,
 					/* For event ID 0xffffffff0000000f - 0xffffffff000000ff */
-					<0xffffffff 0x0 0xffffffff 0xffffff0f 0x00000ff0>,
+					<0xffffffff 0x0 0xffffffff 0xffffff0f 0x00000ff0>;
 };
 ```
 
@@ -90,11 +87,41 @@ pmu {
 /*
  * For HiFive Unmatched board. The encodings can be found here
  * https://sifive.cdn.prismic.io/sifive/1a82e600-1f93-4f41-b2d8-86ed8b16acba_fu740-c000-manual-v1p6.pdf
+ * This example also binds standard SBI PMU hardware id's to U74 PMU event codes, U74 uses bitfield for
+ * events encoding, so several U74 events can be bound to single perf id.
+ * See SBI PMU hardware id's in include/sbi/sbi_ecall_interface.h
  */
 pmu {
 	compatible 			= "riscv,pmu";
-	riscv,raw-event-to-mhpmcounters = <0x0 0x0 0xffffffff 0xfc0000ff 0xc>,
-					  <0x0 0x1 0xffffffff 0xfff800ff 0xc>,
-					  <0x0 0x2 0xffffffff 0xffffe0ff 0xc>;
+	riscv,event-to-mhpmevent =
+/* SBI_PMU_HW_CACHE_REFERENCES -> Instruction cache/ITIM busy | Data cache/DTIM busy */
+				   <0x00003 0x00000000 0x1801>,
+/* SBI_PMU_HW_CACHE_MISSES -> Instruction cache miss | Data cache miss or memory-mapped I/O access */
+				   <0x00004 0x00000000 0x0302>,
+/* SBI_PMU_HW_BRANCH_INSTRUCTIONS -> Conditional branch retired */
+				   <0x00005 0x00000000 0x4000>,
+/* SBI_PMU_HW_BRANCH_MISSES -> Branch direction misprediction | Branch/jump target misprediction */
+				   <0x00006 0x00000000 0x6001>,
+/* L1D_READ_MISS -> Data cache miss or memory-mapped I/O access */
+				   <0x10001 0x00000000 0x0202>,
+/* L1D_WRITE_ACCESS -> Data cache write-back */
+				   <0x10002 0x00000000 0x0402>,
+/* L1I_READ_ACCESS -> Instruction cache miss */
+				   <0x10009 0x00000000 0x0102>,
+/* LL_READ_MISS -> UTLB miss */
+				   <0x10011 0x00000000 0x2002>,
+/* DTLB_READ_MISS -> Data TLB miss */
+				   <0x10019 0x00000000 0x1002>,
+/* ITLB_READ_MISS-> Instruction TLB miss */
+				   <0x10021 0x00000000 0x0802>;
+	riscv,event-to-mhpmcounters = <0x00003 0x00006 0x18>,
+				      <0x10001 0x10002 0x18>,
+				      <0x10009 0x10009 0x18>,
+				      <0x10011 0x10011 0x18>,
+				      <0x10019 0x10019 0x18>,
+				      <0x10021 0x10021 0x18>;
+	riscv,raw-event-to-mhpmcounters = <0x0 0x0 0xffffffff 0xfc0000ff 0x18>,
+					  <0x0 0x1 0xffffffff 0xfff800ff 0x18>,
+					  <0x0 0x2 0xffffffff 0xffffe0ff 0x18>;
 };
 ```
diff --git a/firmware/Kconfig b/firmware/Kconfig
new file mode 100644
index 0000000..d6e0506
--- /dev/null
+++ b/firmware/Kconfig
@@ -0,0 +1 @@
+# SPDX-License-Identifier: BSD-2-Clause
diff --git a/firmware/fw_base.S b/firmware/fw_base.S
index 07be4c4..3f622b3 100644
--- a/firmware/fw_base.S
+++ b/firmware/fw_base.S
@@ -128,9 +128,9 @@ _relocate:
 	REG_L	t1, 0(t1)
 	lla	t2, _load_start
 	REG_L	t2, 0(t2)
+	beq	t0, t2, _relocate_done
 	sub	t3, t1, t0
 	add	t3, t3, t2
-	beq	t0, t2, _relocate_done
 	lla	t4, _relocate_done
 	sub	t4, t4, t2
 	add	t4, t4, t0
diff --git a/firmware/fw_base.ldS b/firmware/fw_base.ldS
index 220c043..563ae25 100644
--- a/firmware/fw_base.ldS
+++ b/firmware/fw_base.ldS
@@ -24,14 +24,12 @@
 		PROVIDE(_text_end = .);
 	}
 
-	. = ALIGN(0x1000); /* Ensure next section is page aligned */
-
 	/* End of the code sections */
 
-	/* Beginning of the read-only data sections */
-
 	. = ALIGN(0x1000); /* Ensure next section is page aligned */
 
+	/* Beginning of the read-only data sections */
+
 	.rodata :
 	{
 		PROVIDE(_rodata_start = .);
@@ -42,10 +40,10 @@
 
 	/* End of the read-only data sections */
 
-	/* Beginning of the read-write data sections */
-
 	. = ALIGN(0x1000); /* Ensure next section is page aligned */
 
+	/* Beginning of the read-write data sections */
+
 	.data :
 	{
 		PROVIDE(_data_start = .);
diff --git a/firmware/payloads/test.elf.ldS b/firmware/payloads/test.elf.ldS
index f3f3242..2328a1b 100644
--- a/firmware/payloads/test.elf.ldS
+++ b/firmware/payloads/test.elf.ldS
@@ -33,14 +33,12 @@ SECTIONS
 		PROVIDE(_text_end = .);
 	}
 
-	. = ALIGN(0x1000); /* Ensure next section is page aligned */
-
 	/* End of the code sections */
 
-	/* Beginning of the read-only data sections */
-
 	. = ALIGN(0x1000); /* Ensure next section is page aligned */
 
+	/* Beginning of the read-only data sections */
+
 	.rodata :
 	{
 		PROVIDE(_rodata_start = .);
@@ -51,10 +49,10 @@ SECTIONS
 
 	/* End of the read-only data sections */
 
-	/* Beginning of the read-write data sections */
-
 	. = ALIGN(0x1000); /* Ensure next section is page aligned */
 
+	/* Beginning of the read-write data sections */
+
 	.data :
 	{
 		PROVIDE(_data_start = .);
diff --git a/include/sbi/riscv_encoding.h b/include/sbi/riscv_encoding.h
index 798afb7..b0f08c8 100644
--- a/include/sbi/riscv_encoding.h
+++ b/include/sbi/riscv_encoding.h
@@ -38,10 +38,14 @@
 #define MSTATUS_SXL			_ULL(0x0000000C00000000)
 #define MSTATUS_SBE			_ULL(0x0000001000000000)
 #define MSTATUS_MBE			_ULL(0x0000002000000000)
+#define MSTATUS_GVA			_ULL(0x0000004000000000)
+#define MSTATUS_GVA_SHIFT		38
 #define MSTATUS_MPV			_ULL(0x0000008000000000)
 #else
 #define MSTATUSH_SBE			_UL(0x00000010)
 #define MSTATUSH_MBE			_UL(0x00000020)
+#define MSTATUSH_GVA			_UL(0x00000040)
+#define MSTATUSH_GVA_SHIFT		6
 #define MSTATUSH_MPV			_UL(0x00000080)
 #endif
 #define MSTATUS32_SD			_UL(0x80000000)
@@ -308,8 +312,6 @@
 
 /* Supervisor Trap Setup */
 #define CSR_SSTATUS			0x100
-#define CSR_SEDELEG			0x102
-#define CSR_SIDELEG			0x103
 #define CSR_SIE				0x104
 #define CSR_STVEC			0x105
 #define CSR_SCOUNTEREN			0x106
diff --git a/include/sbi/sbi_domain.h b/include/sbi/sbi_domain.h
index b90f59c..5553d21 100644
--- a/include/sbi/sbi_domain.h
+++ b/include/sbi/sbi_domain.h
@@ -175,11 +175,25 @@ int sbi_domain_register(struct sbi_domain *dom,
  * @param reg pointer to the memory region to be added
  *
  * @return 0 on success
- * @return SBI_EALREADY if memory region conflicts with existing
+ * @return SBI_EALREADY if memory region conflicts with the existing one
  * @return SBI_EINVAL otherwise
  */
 int sbi_domain_root_add_memregion(const struct sbi_domain_memregion *reg);
 
+/**
+ * Add a memory range with its flags to the root domain
+ * @param addr start physical address of memory range
+ * @param size physical size of memory range
+ * @param align alignment of memory region
+ * @param region_flags memory range flags
+ *
+ * @return 0 on success
+ * @return SBI_EALREADY if memory region conflicts with the existing one
+ * @return SBI_EINVAL otherwise
+ */
+int sbi_domain_root_add_memrange(unsigned long addr, unsigned long size,
+			   unsigned long align, unsigned long region_flags);
+
 /** Finalize domain tables and startup non-root domains */
 int sbi_domain_finalize(struct sbi_scratch *scratch, u32 cold_hartid);
 
diff --git a/include/sbi/sbi_ecall.h b/include/sbi/sbi_ecall.h
index 046a56a..ff9bf8e 100644
--- a/include/sbi/sbi_ecall.h
+++ b/include/sbi/sbi_ecall.h
@@ -31,16 +31,6 @@ struct sbi_ecall_extension {
 		       struct sbi_trap_info *out_trap);
 };
 
-extern struct sbi_ecall_extension ecall_base;
-extern struct sbi_ecall_extension ecall_legacy;
-extern struct sbi_ecall_extension ecall_time;
-extern struct sbi_ecall_extension ecall_rfence;
-extern struct sbi_ecall_extension ecall_ipi;
-extern struct sbi_ecall_extension ecall_vendor;
-extern struct sbi_ecall_extension ecall_hsm;
-extern struct sbi_ecall_extension ecall_srst;
-extern struct sbi_ecall_extension ecall_pmu;
-
 u16 sbi_ecall_version_major(void);
 
 u16 sbi_ecall_version_minor(void);
diff --git a/include/sbi/sbi_ecall_interface.h b/include/sbi/sbi_ecall_interface.h
index 70a3bf7..a3f2bf4 100644
--- a/include/sbi/sbi_ecall_interface.h
+++ b/include/sbi/sbi_ecall_interface.h
@@ -208,6 +208,12 @@ enum sbi_pmu_ctr_type {
 
 #define SBI_PMU_EVENT_IDX_INVALID 0xFFFFFFFF
 
+#define SBI_PMU_EVENT_HW_CACHE_OPS_RESULT	0x1
+#define SBI_PMU_EVENT_HW_CACHE_OPS_ID_MASK	0x6
+#define SBI_PMU_EVENT_HW_CACHE_OPS_ID_OFFSET	1
+#define SBI_PMU_EVENT_HW_CACHE_ID_MASK		0xfff8
+#define SBI_PMU_EVENT_HW_CACHE_ID_OFFSET	3
+
 /* Flags defined for config matching function */
 #define SBI_PMU_CFG_FLAG_SKIP_MATCH	(1 << 0)
 #define SBI_PMU_CFG_FLAG_CLEAR_VALUE	(1 << 1)
diff --git a/include/sbi/sbi_hart.h b/include/sbi/sbi_hart.h
index 4661506..95b40e7 100644
--- a/include/sbi/sbi_hart.h
+++ b/include/sbi/sbi_hart.h
@@ -30,8 +30,8 @@ enum sbi_hart_extensions {
 	SBI_HART_EXT_SSCOFPMF = 0,
 	/** HART has HW time CSR (extension name not available) */
 	SBI_HART_EXT_TIME,
-	/** HART has AIA CSRs (extension name not available) */
-	SBI_HART_EXT_AIA,
+	/** HART has AIA M-mode CSRs */
+	SBI_HART_EXT_SMAIA,
 	/** HART has Smstateen CSR **/
 	SBI_HART_EXT_SMSTATEEN,
 	/** HART has Sstc extension */
@@ -41,6 +41,17 @@ enum sbi_hart_extensions {
 	SBI_HART_EXT_MAX,
 };
 
+struct sbi_hart_features {
+	bool detected;
+	int priv_version;
+	unsigned long extensions;
+	unsigned int pmp_count;
+	unsigned int pmp_addr_bits;
+	unsigned long pmp_gran;
+	unsigned int mhpm_count;
+	unsigned int mhpm_bits;
+};
+
 struct sbi_scratch;
 
 int sbi_hart_reinit(struct sbi_scratch *scratch);
diff --git a/include/sbi/sbi_ipi.h b/include/sbi/sbi_ipi.h
index fb7d658..f6ac807 100644
--- a/include/sbi/sbi_ipi.h
+++ b/include/sbi/sbi_ipi.h
@@ -75,7 +75,7 @@ int sbi_ipi_send_halt(ulong hmask, ulong hbase);
 
 void sbi_ipi_process(void);
 
-void sbi_ipi_raw_send(u32 target_hart);
+int sbi_ipi_raw_send(u32 target_hart);
 
 const struct sbi_ipi_device *sbi_ipi_get_device(void);
 
diff --git a/include/sbi/sbi_platform.h b/include/sbi/sbi_platform.h
index 87024db..722f27a 100644
--- a/include/sbi/sbi_platform.h
+++ b/include/sbi/sbi_platform.h
@@ -48,6 +48,7 @@
 struct sbi_domain_memregion;
 struct sbi_trap_info;
 struct sbi_trap_regs;
+struct sbi_hart_features;
 
 /** Possible feature flags of a platform */
 enum sbi_platform_features {
@@ -90,7 +91,7 @@ struct sbi_platform_operations {
 	int (*misa_get_xlen)(void);
 
 	/** Initialize (or populate) HART extensions for the platform */
-	int (*extensions_init)(void);
+	int (*extensions_init)(struct sbi_hart_features *hfeatures);
 
 	/** Initialize (or populate) domains for the platform */
 	int (*domains_init)(void);
@@ -464,10 +465,11 @@ static inline int sbi_platform_misa_xlen(const struct sbi_platform *plat)
  * @return 0 on success and negative error code on failure
  */
 static inline int sbi_platform_extensions_init(
-					const struct sbi_platform *plat)
+					const struct sbi_platform *plat,
+					struct sbi_hart_features *hfeatures)
 {
 	if (plat && sbi_platform_ops(plat)->extensions_init)
-		return sbi_platform_ops(plat)->extensions_init();
+		return sbi_platform_ops(plat)->extensions_init(hfeatures);
 	return 0;
 }
 
diff --git a/include/sbi/sbi_pmu.h b/include/sbi/sbi_pmu.h
index f5e3dbd..c365243 100644
--- a/include/sbi/sbi_pmu.h
+++ b/include/sbi/sbi_pmu.h
@@ -11,29 +11,90 @@
 #define __SBI_PMU_H__
 
 #include <sbi/sbi_types.h>
-#include <sbi/sbi_hartmask.h>
-#include <sbi/sbi_scratch.h>
-#include <sbi/sbi_ecall_interface.h>
+
+struct sbi_scratch;
 
 /* Event related macros */
 /* Maximum number of hardware events that can mapped by OpenSBI */
 #define SBI_PMU_HW_EVENT_MAX 256
 
-/* Maximum number of firmware events that can mapped by OpenSBI */
-#define SBI_PMU_FW_EVENT_MAX 32
-
 /* Counter related macros */
 #define SBI_PMU_FW_CTR_MAX 16
 #define SBI_PMU_HW_CTR_MAX 32
 #define SBI_PMU_CTR_MAX	   (SBI_PMU_HW_CTR_MAX + SBI_PMU_FW_CTR_MAX)
 #define SBI_PMU_FIXED_CTR_MASK 0x07
 
+struct sbi_pmu_device {
+	/** Name of the PMU platform device */
+	char name[32];
+
+	/**
+	 * Validate event code of custom firmware event
+	 * Note: SBI_PMU_FW_MAX <= event_idx_code
+	 */
+	int (*fw_event_validate_code)(uint32_t event_idx_code);
+
+	/**
+	 * Match custom firmware counter with custom firmware event
+	 * Note: 0 <= counter_index < SBI_PMU_FW_CTR_MAX
+	 */
+	bool (*fw_counter_match_code)(uint32_t counter_index,
+				      uint32_t event_idx_code);
+
+	/**
+	 * Read value of custom firmware counter
+	 * Note: 0 <= counter_index < SBI_PMU_FW_CTR_MAX
+	 */
+	uint64_t (*fw_counter_read_value)(uint32_t counter_index);
+
+	/**
+	 * Start custom firmware counter
+	 * Note: SBI_PMU_FW_MAX <= event_idx_code
+	 * Note: 0 <= counter_index < SBI_PMU_FW_CTR_MAX
+	 */
+	int (*fw_counter_start)(uint32_t counter_index,
+				uint32_t event_idx_code,
+				uint64_t init_val, bool init_val_update);
+
+	/**
+	 * Stop custom firmware counter
+	 * Note: 0 <= counter_index < SBI_PMU_FW_CTR_MAX
+	 */
+	int (*fw_counter_stop)(uint32_t counter_index);
+
+	/**
+	 * Custom enable irq for hardware counter
+	 * Note: 0 <= counter_index < SBI_PMU_HW_CTR_MAX
+	 */
+	void (*hw_counter_enable_irq)(uint32_t counter_index);
+
+	/**
+	 * Custom disable irq for hardware counter
+	 * Note: 0 <= counter_index < SBI_PMU_HW_CTR_MAX
+	 */
+	void (*hw_counter_disable_irq)(uint32_t counter_index);
+
+	/**
+	 * Custom function returning the machine-specific irq-bit.
+	 */
+	int (*hw_counter_irq_bit)(void);
+};
+
+/** Get the PMU platform device */
+const struct sbi_pmu_device *sbi_pmu_get_device(void);
+
+/** Set the PMU platform device */
+void sbi_pmu_set_device(const struct sbi_pmu_device *dev);
+
 /** Initialize PMU */
 int sbi_pmu_init(struct sbi_scratch *scratch, bool cold_boot);
 
 /** Reset PMU during hart exit */
 void sbi_pmu_exit(struct sbi_scratch *scratch);
 
+/** Return the pmu irq bit depending on extension existence */
+int sbi_pmu_irq_bit(void);
+
 /**
  * Add the hardware event to counter mapping information. This should be called
  * from the platform code to update the mapping table.
@@ -53,7 +114,7 @@ int sbi_pmu_add_hw_event_counter_map(u32 eidx_start, u32 eidx_end, u32 cmap);
 
 int sbi_pmu_add_raw_event_counter_map(uint64_t select, uint64_t select_mask, u32 cmap);
 
-int sbi_pmu_ctr_read(uint32_t cidx, unsigned long *cval);
+int sbi_pmu_ctr_fw_read(uint32_t cidx, uint64_t *cval);
 
 int sbi_pmu_ctr_stop(unsigned long cidx_base, unsigned long cidx_mask,
 		     unsigned long flag);
diff --git a/include/sbi/sbi_scratch.h b/include/sbi/sbi_scratch.h
index 2f6f0a1..40a3bc9 100644
--- a/include/sbi/sbi_scratch.h
+++ b/include/sbi/sbi_scratch.h
@@ -57,7 +57,7 @@ struct sbi_scratch {
 	unsigned long next_arg1;
 	/** Address of next booting stage for this HART */
 	unsigned long next_addr;
-	/** Priviledge mode of next booting stage for this HART */
+	/** Privilege mode of next booting stage for this HART */
 	unsigned long next_mode;
 	/** Warm boot entry point address for this HART */
 	unsigned long warmboot_addr;
diff --git a/include/sbi/sbi_timer.h b/include/sbi/sbi_timer.h
index 63ef1af..ac48e2b 100644
--- a/include/sbi/sbi_timer.h
+++ b/include/sbi/sbi_timer.h
@@ -48,6 +48,24 @@ static inline void sbi_timer_udelay(ulong usecs)
 	sbi_timer_delay_loop(usecs, 1000000, NULL, NULL);
 }
 
+/**
+ * A blocking function that will wait until @p predicate returns true or
+ * @p timeout_ms milliseconds elapsed. @p arg will be passed as argument to
+ * @p predicate function.
+ *
+ * @param predicate Pointer to a function that returns true if certain
+ * condition is met. It shouldn't block the code execution.
+ * @param arg Argument to pass to @p predicate.
+ * @param timeout_ms Timeout value in milliseconds. The function will return
+ * false if @p timeout_ms time period elapsed but still @p predicate doesn't
+ * return true.
+ *
+ * @return true if @p predicate returns true within @p timeout_ms, false
+ * otherwise.
+ */
+bool sbi_timer_waitms_until(bool (*predicate)(void *), void *arg,
+			    uint64_t timeout_ms);
+
 /** Get timer value for current HART */
 u64 sbi_timer_value(void);
 
diff --git a/include/sbi/sbi_trap.h b/include/sbi/sbi_trap.h
index d205056..a562b95 100644
--- a/include/sbi/sbi_trap.h
+++ b/include/sbi/sbi_trap.h
@@ -10,6 +10,8 @@
 #ifndef __SBI_TRAP_H__
 #define __SBI_TRAP_H__
 
+#include <sbi/riscv_encoding.h>
+
 /* clang-format off */
 
 /** Index of zero member in sbi_trap_regs */
@@ -95,8 +97,10 @@
 #define SBI_TRAP_INFO_tval2			3
 /** Index of tinst member in sbi_trap_info */
 #define SBI_TRAP_INFO_tinst			4
+/** Index of gva member in sbi_trap_info */
+#define SBI_TRAP_INFO_gva			5
 /** Last member index in sbi_trap_info */
-#define SBI_TRAP_INFO_last			5
+#define SBI_TRAP_INFO_last			6
 
 /* clang-format on */
 
@@ -200,8 +204,26 @@ struct sbi_trap_info {
 	unsigned long tval2;
 	/** tinst Trap instruction */
 	unsigned long tinst;
+	/** gva Guest virtual address in tval flag */
+	unsigned long gva;
 };
 
+static inline unsigned long sbi_regs_gva(const struct sbi_trap_regs *regs)
+{
+	/*
+	 * If the hypervisor extension is not implemented, mstatus[h].GVA is a
+	 * WPRI field, which is guaranteed to read as zero. In addition, in this
+	 * case we don't read mstatush and instead pretend it is zero, which
+	 * handles privileged spec version < 1.12.
+	 */
+
+#if __riscv_xlen == 32
+	return (regs->mstatusH & MSTATUSH_GVA) ? 1 : 0;
+#else
+	return (regs->mstatus & MSTATUS_GVA) ? 1 : 0;
+#endif
+}
+
 int sbi_trap_redirect(struct sbi_trap_regs *regs,
 		      struct sbi_trap_info *trap);
 
diff --git a/include/sbi/sbi_version.h b/include/sbi/sbi_version.h
index f687f30..dd75dea 100644
--- a/include/sbi/sbi_version.h
+++ b/include/sbi/sbi_version.h
@@ -11,7 +11,7 @@
 #define __SBI_VERSION_H__
 
 #define OPENSBI_VERSION_MAJOR 1
-#define OPENSBI_VERSION_MINOR 1
+#define OPENSBI_VERSION_MINOR 2
 
 /**
  * OpenSBI 32-bit version with:
diff --git a/include/sbi_utils/fdt/fdt_domain.h b/include/sbi_utils/fdt/fdt_domain.h
index 5448eb4..ee09d1b 100644
--- a/include/sbi_utils/fdt/fdt_domain.h
+++ b/include/sbi_utils/fdt/fdt_domain.h
@@ -13,6 +13,8 @@
 
 #include <sbi/sbi_types.h>
 
+#ifdef CONFIG_FDT_DOMAIN
+
 struct sbi_domain;
 
 /**
@@ -70,4 +72,11 @@ void fdt_domain_fixup(void *fdt);
  */
 int fdt_domains_populate(void *fdt);
 
+#else
+
+static inline void fdt_domain_fixup(void *fdt) { }
+static inline int fdt_domains_populate(void *fdt) { return 0; }
+
+#endif
+
 #endif /* __FDT_DOMAIN_H__ */
diff --git a/include/sbi_utils/fdt/fdt_helper.h b/include/sbi_utils/fdt/fdt_helper.h
index c60af35..09f3095 100644
--- a/include/sbi_utils/fdt/fdt_helper.h
+++ b/include/sbi_utils/fdt/fdt_helper.h
@@ -59,14 +59,17 @@ int fdt_parse_timebase_frequency(void *fdt, unsigned long *freq);
 int fdt_parse_gaisler_uart_node(void *fdt, int nodeoffset,
 				struct platform_uart_data *uart);
 
+int fdt_parse_renesas_scif_node(void *fdt, int nodeoffset,
+				struct platform_uart_data *uart);
+
 int fdt_parse_shakti_uart_node(void *fdt, int nodeoffset,
 			       struct platform_uart_data *uart);
 
 int fdt_parse_sifive_uart_node(void *fdt, int nodeoffset,
 			       struct platform_uart_data *uart);
 
-int fdt_parse_uart8250_node(void *fdt, int nodeoffset,
-			    struct platform_uart_data *uart);
+int fdt_parse_uart_node(void *fdt, int nodeoffset,
+			struct platform_uart_data *uart);
 
 int fdt_parse_uart8250(void *fdt, struct platform_uart_data *uart,
 		       const char *compatible);
@@ -95,6 +98,12 @@ int fdt_parse_aclint_node(void *fdt, int nodeoffset, bool for_timer,
 			  unsigned long *out_addr2, unsigned long *out_size2,
 			  u32 *out_first_hartid, u32 *out_hart_count);
 
+int fdt_parse_plmt_node(void *fdt, int nodeoffset, unsigned long *plmt_base,
+			  unsigned long *plmt_size, u32 *hart_count);
+
+int fdt_parse_plicsw_node(void *fdt, int nodeoffset, unsigned long *plicsw_base,
+			  unsigned long *size, u32 *hart_count);
+
 int fdt_parse_compat_addr(void *fdt, uint64_t *addr,
 			  const char *compatible);
 
diff --git a/include/sbi_utils/fdt/fdt_pmu.h b/include/sbi_utils/fdt/fdt_pmu.h
index 2fa01ed..c65cad7 100644
--- a/include/sbi_utils/fdt/fdt_pmu.h
+++ b/include/sbi_utils/fdt/fdt_pmu.h
@@ -13,6 +13,8 @@
 
 #include <sbi/sbi_types.h>
 
+#ifdef CONFIG_FDT_PMU
+
 /**
  * Fix up the PMU node in the device tree
  *
@@ -43,4 +45,12 @@ int fdt_pmu_setup(void *fdt);
  */
 uint64_t fdt_pmu_get_select_value(uint32_t event_idx);
 
+#else
+
+static inline void fdt_pmu_fixup(void *fdt) { }
+static inline int fdt_pmu_setup(void *fdt) { return 0; }
+static inline uint64_t fdt_pmu_get_select_value(uint32_t event_idx) { return 0; }
+
+#endif
+
 #endif
diff --git a/include/sbi_utils/ipi/andes_plicsw.h b/include/sbi_utils/ipi/andes_plicsw.h
new file mode 100644
index 0000000..e93cda0
--- /dev/null
+++ b/include/sbi_utils/ipi/andes_plicsw.h
@@ -0,0 +1,46 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Zong Li <zong@andestech.com>
+ *   Nylon Chen <nylon7@andestech.com>
+ *   Leo Yu-Chi Liang <ycliang@andestech.com>
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#ifndef _IPI_ANDES_PLICSW_H_
+#define _IPI_ANDES_PLICSW_H_
+
+#define PLICSW_PRIORITY_BASE 0x4
+
+#define PLICSW_PENDING_BASE 0x1000
+#define PLICSW_PENDING_STRIDE 0x8
+
+#define PLICSW_ENABLE_BASE 0x2000
+#define PLICSW_ENABLE_STRIDE 0x80
+
+#define PLICSW_CONTEXT_BASE 0x200000
+#define PLICSW_CONTEXT_STRIDE 0x1000
+#define PLICSW_CONTEXT_CLAIM 0x4
+
+#define PLICSW_HART_MASK 0x01010101
+
+#define PLICSW_HART_MAX_NR 8
+
+#define PLICSW_REGION_ALIGN 0x1000
+
+struct plicsw_data {
+	unsigned long addr;
+	unsigned long size;
+	uint32_t hart_count;
+	/* hart id to source id table */
+	uint32_t source_id[PLICSW_HART_MAX_NR];
+};
+
+int plicsw_warm_ipi_init(void);
+
+int plicsw_cold_ipi_init(struct plicsw_data *plicsw);
+
+#endif /* _IPI_ANDES_PLICSW_H_ */
diff --git a/include/sbi_utils/ipi/fdt_ipi.h b/include/sbi_utils/ipi/fdt_ipi.h
index 9337353..1dd9062 100644
--- a/include/sbi_utils/ipi/fdt_ipi.h
+++ b/include/sbi_utils/ipi/fdt_ipi.h
@@ -12,6 +12,8 @@
 
 #include <sbi/sbi_types.h>
 
+#ifdef CONFIG_FDT_IPI
+
 struct fdt_ipi {
 	const struct fdt_match *match_table;
 	int (*cold_init)(void *fdt, int nodeoff, const struct fdt_match *match);
@@ -23,4 +25,11 @@ void fdt_ipi_exit(void);
 
 int fdt_ipi_init(bool cold_boot);
 
+#else
+
+static inline void fdt_ipi_exit(void) { }
+static inline int fdt_ipi_init(bool cold_boot) { return 0; }
+
+#endif
+
 #endif
diff --git a/include/sbi_utils/irqchip/fdt_irqchip.h b/include/sbi_utils/irqchip/fdt_irqchip.h
index 13ef6f7..63e9fd9 100644
--- a/include/sbi_utils/irqchip/fdt_irqchip.h
+++ b/include/sbi_utils/irqchip/fdt_irqchip.h
@@ -12,6 +12,8 @@
 
 #include <sbi/sbi_types.h>
 
+#ifdef CONFIG_FDT_IRQCHIP
+
 struct fdt_irqchip {
 	const struct fdt_match *match_table;
 	int (*cold_init)(void *fdt, int nodeoff, const struct fdt_match *match);
@@ -23,4 +25,12 @@ void fdt_irqchip_exit(void);
 
 int fdt_irqchip_init(bool cold_boot);
 
+#else
+
+static inline void fdt_irqchip_exit(void) { }
+
+static inline int fdt_irqchip_init(bool cold_boot) { return 0; }
+
+#endif
+
 #endif
diff --git a/include/sbi_utils/irqchip/fdt_irqchip_plic.h b/include/sbi_utils/irqchip/fdt_irqchip_plic.h
index 98d4de5..df645dd 100644
--- a/include/sbi_utils/irqchip/fdt_irqchip_plic.h
+++ b/include/sbi_utils/irqchip/fdt_irqchip_plic.h
@@ -9,13 +9,24 @@
 
 #include <sbi/sbi_types.h>
 
-void fdt_plic_priority_save(u8 *priority);
+/**
+ * Save the PLIC priority state
+ * @param priority pointer to the memory region for the saved priority
+ * @param num size of the memory region including interrupt source 0
+ */
+void fdt_plic_priority_save(u8 *priority, u32 num);
 
-void fdt_plic_priority_restore(const u8 *priority);
+/**
+ * Restore the PLIC priority state
+ * @param priority pointer to the memory region for the saved priority
+ * @param num size of the memory region including interrupt source 0
+ */
+void fdt_plic_priority_restore(const u8 *priority, u32 num);
 
-void fdt_plic_context_save(bool smode, u32 *enable, u32 *threshold);
+void fdt_plic_context_save(bool smode, u32 *enable, u32 *threshold, u32 num);
 
-void fdt_plic_context_restore(bool smode, const u32 *enable, u32 threshold);
+void fdt_plic_context_restore(bool smode, const u32 *enable, u32 threshold,
+			      u32 num);
 
 void thead_plic_restore(void);
 
diff --git a/include/sbi_utils/irqchip/imsic.h b/include/sbi_utils/irqchip/imsic.h
index cffcb5a..e295771 100644
--- a/include/sbi_utils/irqchip/imsic.h
+++ b/include/sbi_utils/irqchip/imsic.h
@@ -33,6 +33,8 @@ struct imsic_data {
 	struct imsic_regs regs[IMSIC_MAX_REGS];
 };
 
+#ifdef CONFIG_IRQCHIP_IMSIC
+
 int imsic_map_hartid_to_data(u32 hartid, struct imsic_data *imsic, int file);
 
 struct imsic_data *imsic_get_data(u32 hartid);
@@ -47,4 +49,12 @@ int imsic_data_check(struct imsic_data *imsic);
 
 int imsic_cold_irqchip_init(struct imsic_data *imsic);
 
+#else
+
+static inline void imsic_local_irqchip_init(void) { }
+
+static inline int imsic_data_check(struct imsic_data *imsic) { return 0; }
+
+#endif
+
 #endif
diff --git a/include/sbi_utils/irqchip/plic.h b/include/sbi_utils/irqchip/plic.h
index 48c24f0..112a714 100644
--- a/include/sbi_utils/irqchip/plic.h
+++ b/include/sbi_utils/irqchip/plic.h
@@ -18,15 +18,16 @@ struct plic_data {
 };
 
 /* So far, priorities on all consumers of these functions fit in 8 bits. */
-void plic_priority_save(const struct plic_data *plic, u8 *priority);
+void plic_priority_save(const struct plic_data *plic, u8 *priority, u32 num);
 
-void plic_priority_restore(const struct plic_data *plic, const u8 *priority);
+void plic_priority_restore(const struct plic_data *plic, const u8 *priority,
+			   u32 num);
 
 void plic_context_save(const struct plic_data *plic, int context_id,
-		       u32 *enable, u32 *threshold);
+		       u32 *enable, u32 *threshold, u32 num);
 
 void plic_context_restore(const struct plic_data *plic, int context_id,
-			  const u32 *enable, u32 threshold);
+			  const u32 *enable, u32 threshold, u32 num);
 
 int plic_context_init(const struct plic_data *plic, int context_id,
 		      bool enable, u32 threshold);
diff --git a/include/sbi_utils/reset/fdt_reset.h b/include/sbi_utils/reset/fdt_reset.h
index e7f7350..ea8063b 100644
--- a/include/sbi_utils/reset/fdt_reset.h
+++ b/include/sbi_utils/reset/fdt_reset.h
@@ -17,6 +17,8 @@ struct fdt_reset {
 	int (*init)(void *fdt, int nodeoff, const struct fdt_match *match);
 };
 
+#ifdef CONFIG_FDT_RESET
+
 /**
  * fdt_reset_driver_init() - initialize reset driver based on the device-tree
  */
@@ -29,4 +31,14 @@ int fdt_reset_driver_init(void *fdt, struct fdt_reset *drv);
  */
 void fdt_reset_init(void);
 
+#else
+
+static inline int fdt_reset_driver_init(void *fdt, struct fdt_reset *drv)
+{
+	return 0;
+}
+static inline void fdt_reset_init(void) { }
+
+#endif
+
 #endif
diff --git a/include/sbi_utils/serial/cadence-uart.h b/include/sbi_utils/serial/cadence-uart.h
new file mode 100644
index 0000000..e75fb95
--- /dev/null
+++ b/include/sbi_utils/serial/cadence-uart.h
@@ -0,0 +1,16 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 StarFive Technology Co., Ltd.
+ *
+ * Author: Jun Liang Tan <junliang.tan@linux.starfivetech.com>
+ */
+
+#ifndef __SERIAL_CADENCE_UART_H__
+#define __SERIAL_CADENCE_UART_H__
+
+#include <sbi/sbi_types.h>
+
+int cadence_uart_init(unsigned long base, u32 in_freq, u32 baudrate);
+
+#endif
diff --git a/include/sbi_utils/serial/fdt_serial.h b/include/sbi_utils/serial/fdt_serial.h
index 6451c23..daa2e4f 100644
--- a/include/sbi_utils/serial/fdt_serial.h
+++ b/include/sbi_utils/serial/fdt_serial.h
@@ -12,6 +12,8 @@
 
 #include <sbi/sbi_types.h>
 
+#ifdef CONFIG_FDT_SERIAL
+
 struct fdt_serial {
 	const struct fdt_match *match_table;
 	int (*init)(void *fdt, int nodeoff, const struct fdt_match *match);
@@ -19,4 +21,10 @@ struct fdt_serial {
 
 int fdt_serial_init(void);
 
+#else
+
+static inline int fdt_serial_init(void) { return 0; }
+
+#endif
+
 #endif
diff --git a/include/sbi_utils/serial/renesas-scif.h b/include/sbi_utils/serial/renesas-scif.h
new file mode 100644
index 0000000..0002a1a
--- /dev/null
+++ b/include/sbi_utils/serial/renesas-scif.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#ifndef __SERIAL_RENESAS_SCIF_H__
+#define __SERIAL_RENESAS_SCIF_H__
+
+int renesas_scif_init(unsigned long base, u32 in_freq, u32 baudrate);
+
+#endif /* __SERIAL_RENESAS_SCIF_H__ */
diff --git a/include/sbi_utils/serial/semihosting.h b/include/sbi_utils/serial/semihosting.h
new file mode 100644
index 0000000..8cc4a86
--- /dev/null
+++ b/include/sbi_utils/serial/semihosting.h
@@ -0,0 +1,47 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ *
+ * Authors:
+ *   Anup Patel <apatel@ventanamicro.com>
+ *   Kautuk Consul <kconsul@ventanamicro.com>
+ */
+
+#ifndef __SERIAL_SEMIHOSTING_H__
+#define __SERIAL_SEMIHOSTING_H__
+
+#include <sbi/sbi_types.h>
+
+/**
+ * enum semihosting_open_mode - Numeric file modes for use with semihosting_open()
+ * MODE_READ: 'r'
+ * MODE_BINARY: 'b'
+ * MODE_PLUS: '+'
+ * MODE_WRITE: 'w'
+ * MODE_APPEND: 'a'
+ *
+ * These modes represent the mode string used by fopen(3) in a form which can
+ * be passed to semihosting_open(). These do NOT correspond directly to %O_RDONLY,
+ * %O_CREAT, etc; see fopen(3) for details. In particular, @MODE_PLUS
+ * effectively results in adding %O_RDWR, and @MODE_WRITE will add %O_TRUNC.
+ * For compatibility, @MODE_BINARY should be added when opening non-text files
+ * (such as images).
+ */
+enum semihosting_open_mode {
+	MODE_READ	= 0x0,
+	MODE_BINARY	= 0x1,
+	MODE_PLUS	= 0x2,
+	MODE_WRITE	= 0x4,
+	MODE_APPEND	= 0x8,
+};
+
+#ifdef CONFIG_SERIAL_SEMIHOSTING
+int semihosting_init(void);
+int semihosting_enabled(void);
+#else
+static inline int semihosting_init(void) { return SBI_ENODEV; }
+static inline int semihosting_enabled(void) { return 0; }
+#endif
+
+#endif
diff --git a/include/sbi_utils/timer/aclint_mtimer.h b/include/sbi_utils/timer/aclint_mtimer.h
index f02cc62..6ab8799 100644
--- a/include/sbi_utils/timer/aclint_mtimer.h
+++ b/include/sbi_utils/timer/aclint_mtimer.h
@@ -22,6 +22,8 @@
 
 #define CLINT_MTIMER_OFFSET		0x4000
 
+#define MTIMER_REGION_ALIGN		0x1000
+
 struct aclint_mtimer_data {
 	/* Public details */
 	unsigned long mtime_freq;
diff --git a/include/sbi_utils/timer/andes_plmt.h b/include/sbi_utils/timer/andes_plmt.h
new file mode 100644
index 0000000..08bce33
--- /dev/null
+++ b/include/sbi_utils/timer/andes_plmt.h
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Zong Li <zong@andestech.com>
+ *   Nylon Chen <nylon7@andestech.com>
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#ifndef __TIMER_ANDES_PLMT_H__
+#define __TIMER_ANDES_PLMT_H__
+
+#define DEFAULT_AE350_PLMT_FREQ 60000000
+#define PLMT_REGION_ALIGN 0x1000
+
+struct plmt_data {
+	u32 hart_count;
+	unsigned long size;
+	unsigned long timer_freq;
+	volatile u64 *time_val;
+	volatile u64 *time_cmp;
+};
+
+int plmt_cold_timer_init(struct plmt_data *plmt);
+int plmt_warm_timer_init(void);
+
+#endif /* __TIMER_ANDES_PLMT_H__ */
diff --git a/include/sbi_utils/timer/fdt_timer.h b/include/sbi_utils/timer/fdt_timer.h
index 36202a4..cf105fe 100644
--- a/include/sbi_utils/timer/fdt_timer.h
+++ b/include/sbi_utils/timer/fdt_timer.h
@@ -12,6 +12,8 @@
 
 #include <sbi/sbi_types.h>
 
+#ifdef CONFIG_FDT_TIMER
+
 struct fdt_timer {
 	const struct fdt_match *match_table;
 	int (*cold_init)(void *fdt, int nodeoff, const struct fdt_match *match);
@@ -23,4 +25,11 @@ void fdt_timer_exit(void);
 
 int fdt_timer_init(bool cold_boot);
 
+#else
+
+static inline void fdt_timer_exit(void) { }
+static inline int fdt_timer_init(bool cold_boot) { return 0; }
+
+#endif
+
 #endif
diff --git a/lib/sbi/Kconfig b/lib/sbi/Kconfig
new file mode 100644
index 0000000..df74bba
--- /dev/null
+++ b/lib/sbi/Kconfig
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "SBI Extension Support"
+
+config SBI_ECALL_TIME
+	bool "Timer extension"
+	default y
+
+config SBI_ECALL_RFENCE
+	bool "RFENCE extension"
+	default y
+
+config SBI_ECALL_IPI
+	bool "IPI extension"
+	default y
+
+config SBI_ECALL_HSM
+	bool "Hart State Management extension"
+	default y
+
+config SBI_ECALL_SRST
+	bool "System Reset extension"
+	default y
+
+config SBI_ECALL_PMU
+	bool "Performance Monitoring Unit extension"
+	default y
+
+config SBI_ECALL_LEGACY
+	bool "SBI v0.1 legacy extensions"
+	default y
+
+config SBI_ECALL_VENDOR
+	bool "Platform-defined vendor extensions"
+	default y
+
+endmenu
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index 1ed1983..c774ebb 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -12,17 +12,41 @@ libsbi-objs-y += riscv_atomic.o
 libsbi-objs-y += riscv_hardfp.o
 libsbi-objs-y += riscv_locks.o
 
+libsbi-objs-y += sbi_ecall.o
+libsbi-objs-y += sbi_ecall_exts.o
+
+# The order of below extensions is performance optimized
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_TIME) += ecall_time
+libsbi-objs-$(CONFIG_SBI_ECALL_TIME) += sbi_ecall_time.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_RFENCE) += ecall_rfence
+libsbi-objs-$(CONFIG_SBI_ECALL_RFENCE) += sbi_ecall_rfence.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_IPI) += ecall_ipi
+libsbi-objs-$(CONFIG_SBI_ECALL_IPI) += sbi_ecall_ipi.o
+
+carray-sbi_ecall_exts-y += ecall_base
+libsbi-objs-y += sbi_ecall_base.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_HSM) += ecall_hsm
+libsbi-objs-$(CONFIG_SBI_ECALL_HSM) += sbi_ecall_hsm.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_SRST) += ecall_srst
+libsbi-objs-$(CONFIG_SBI_ECALL_SRST) += sbi_ecall_srst.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_PMU) += ecall_pmu
+libsbi-objs-$(CONFIG_SBI_ECALL_PMU) += sbi_ecall_pmu.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_LEGACY) += ecall_legacy
+libsbi-objs-$(CONFIG_SBI_ECALL_LEGACY) += sbi_ecall_legacy.o
+
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_VENDOR) += ecall_vendor
+libsbi-objs-$(CONFIG_SBI_ECALL_VENDOR) += sbi_ecall_vendor.o
+
 libsbi-objs-y += sbi_bitmap.o
 libsbi-objs-y += sbi_bitops.o
 libsbi-objs-y += sbi_console.o
 libsbi-objs-y += sbi_domain.o
-libsbi-objs-y += sbi_ecall.o
-libsbi-objs-y += sbi_ecall_base.o
-libsbi-objs-y += sbi_ecall_hsm.o
-libsbi-objs-y += sbi_ecall_legacy.o
-libsbi-objs-y += sbi_ecall_pmu.o
-libsbi-objs-y += sbi_ecall_replace.o
-libsbi-objs-y += sbi_ecall_vendor.o
 libsbi-objs-y += sbi_emulate_csr.o
 libsbi-objs-y += sbi_fifo.o
 libsbi-objs-y += sbi_hart.o
diff --git a/lib/sbi/sbi_console.c b/lib/sbi/sbi_console.c
index 34c843d..cfef2f8 100644
--- a/lib/sbi/sbi_console.c
+++ b/lib/sbi/sbi_console.c
@@ -76,20 +76,22 @@ typedef __builtin_va_list va_list;
 
 static void printc(char **out, u32 *out_len, char ch)
 {
-	if (out) {
-		if (*out) {
-			if (out_len && (0 < *out_len)) {
-				**out = ch;
-				++(*out);
-				(*out_len)--;
-			} else {
-				**out = ch;
-				++(*out);
-			}
-		}
-	} else {
+	if (!out) {
 		sbi_putc(ch);
+		return;
+	}
+
+	/*
+	 * The *printf entry point functions have enforced that (*out) can
+	 * only be null when out_len is non-null and its value is zero.
+	 */
+	if (!out_len || *out_len > 1) {
+		*(*out)++ = ch;
+		**out = '\0';
 	}
+
+	if (out_len && *out_len > 0)
+		--(*out_len);
 }
 
 static int prints(char **out, u32 *out_len, const char *string, int width,
@@ -181,7 +183,7 @@ static int printi(char **out, u32 *out_len, long long i, int b, int sg,
 
 static int print(char **out, u32 *out_len, const char *format, va_list args)
 {
-	int width, flags, acnt = 0;
+	int width, flags;
 	int pc = 0;
 	char scr[2];
 	unsigned long long tmp;
@@ -193,7 +195,7 @@ static int print(char **out, u32 *out_len, const char *format, va_list args)
 			if (*format == '\0')
 				break;
 			if (*format == '%')
-				goto out;
+				goto literal;
 			/* Get flags */
 			if (*format == '-') {
 				++format;
@@ -214,7 +216,6 @@ static int print(char **out, u32 *out_len, const char *format, va_list args)
 			}
 			if (*format == 's') {
 				char *s = va_arg(args, char *);
-				acnt += sizeof(char *);
 				pc += prints(out, out_len, s ? s : "(null)",
 					     width, flags);
 				continue;
@@ -222,61 +223,40 @@ static int print(char **out, u32 *out_len, const char *format, va_list args)
 			if ((*format == 'd') || (*format == 'i')) {
 				pc += printi(out, out_len, va_arg(args, int),
 					     10, 1, width, flags, '0');
-				acnt += sizeof(int);
 				continue;
 			}
 			if (*format == 'x') {
 				pc += printi(out, out_len,
 					     va_arg(args, unsigned int), 16, 0,
 					     width, flags, 'a');
-				acnt += sizeof(unsigned int);
 				continue;
 			}
 			if (*format == 'X') {
 				pc += printi(out, out_len,
 					     va_arg(args, unsigned int), 16, 0,
 					     width, flags, 'A');
-				acnt += sizeof(unsigned int);
 				continue;
 			}
 			if (*format == 'u') {
 				pc += printi(out, out_len,
 					     va_arg(args, unsigned int), 10, 0,
 					     width, flags, 'a');
-				acnt += sizeof(unsigned int);
 				continue;
 			}
 			if (*format == 'p') {
 				pc += printi(out, out_len,
 					     va_arg(args, unsigned long), 16, 0,
 					     width, flags, 'a');
-				acnt += sizeof(unsigned long);
 				continue;
 			}
 			if (*format == 'P') {
 				pc += printi(out, out_len,
 					     va_arg(args, unsigned long), 16, 0,
 					     width, flags, 'A');
-				acnt += sizeof(unsigned long);
 				continue;
 			}
 			if (*format == 'l' && *(format + 1) == 'l') {
-				while (acnt &
-				       (sizeof(unsigned long long) - 1)) {
-					va_arg(args, int);
-					acnt += sizeof(int);
-				}
-				if (sizeof(unsigned long long) ==
-				    sizeof(unsigned long)) {
-					tmp = va_arg(args, unsigned long long);
-					acnt += sizeof(unsigned long long);
-				} else {
-					((unsigned long *)&tmp)[0] =
-						va_arg(args, unsigned long);
-					((unsigned long *)&tmp)[1] =
-						va_arg(args, unsigned long);
-					acnt += 2 * sizeof(unsigned long);
-				}
+				tmp = va_arg(args, unsigned long long);
 				if (*(format + 2) == 'u') {
 					format += 2;
 					pc += printi(out, out_len, tmp, 10, 0,
@@ -308,19 +288,16 @@ static int print(char **out, u32 *out_len, const char *format, va_list args)
 						out, out_len,
 						va_arg(args, unsigned long), 16,
 						0, width, flags, 'a');
-					acnt += sizeof(unsigned long);
 				} else if (*(format + 1) == 'X') {
 					format += 1;
 					pc += printi(
 						out, out_len,
 						va_arg(args, unsigned long), 16,
 						0, width, flags, 'A');
-					acnt += sizeof(unsigned long);
 				} else {
 					pc += printi(out, out_len,
 						     va_arg(args, long), 10, 1,
 						     width, flags, '0');
-					acnt += sizeof(long);
 				}
 			}
 			if (*format == 'c') {
@@ -328,17 +305,14 @@ static int print(char **out, u32 *out_len, const char *format, va_list args)
 				scr[0] = va_arg(args, int);
 				scr[1] = '\0';
 				pc += prints(out, out_len, scr, width, flags);
-				acnt += sizeof(int);
 				continue;
 			}
 		} else {
-		out:
+literal:
 			printc(out, out_len, *format);
 			++pc;
 		}
 	}
-	if (out)
-		**out = '\0';
 
 	return pc;
 }
@@ -348,6 +322,9 @@ int sbi_sprintf(char *out, const char *format, ...)
 	va_list args;
 	int retval;
 
+	if (unlikely(!out))
+		sbi_panic("sbi_sprintf called with NULL output string\n");
+
 	va_start(args, format);
 	retval = print(&out, NULL, format, args);
 	va_end(args);
@@ -360,6 +337,10 @@ int sbi_snprintf(char *out, u32 out_sz, const char *format, ...)
 	va_list args;
 	int retval;
 
+	if (unlikely(!out && out_sz != 0))
+		sbi_panic("sbi_snprintf called with NULL output string and "
+			  "output size is not zero\n");
+
 	va_start(args, format);
 	retval = print(&out, &out_sz, format, args);
 	va_end(args);
diff --git a/lib/sbi/sbi_domain.c b/lib/sbi/sbi_domain.c
index 4e4c1e1..3205595 100644
--- a/lib/sbi/sbi_domain.c
+++ b/lib/sbi/sbi_domain.c
@@ -105,7 +105,7 @@ bool sbi_domain_check_addr(const struct sbi_domain *dom,
 			   unsigned long addr, unsigned long mode,
 			   unsigned long access_flags)
 {
-	bool mmio = FALSE;
+	bool rmmio, mmio = FALSE;
 	struct sbi_domain_memregion *reg;
 	unsigned long rstart, rend, rflags, rwx = 0;
 
@@ -130,8 +130,8 @@ bool sbi_domain_check_addr(const struct sbi_domain *dom,
 		rend = (reg->order < __riscv_xlen) ?
 			rstart + ((1UL << reg->order) - 1) : -1UL;
 		if (rstart <= addr && addr <= rend) {
-			if ((mmio && !(rflags & SBI_DOMAIN_MEMREGION_MMIO)) ||
-			    (!mmio && (rflags & SBI_DOMAIN_MEMREGION_MMIO)))
+			rmmio = (rflags & SBI_DOMAIN_MEMREGION_MMIO) ? TRUE : FALSE;
+			if (mmio != rmmio)
 				return FALSE;
 			return ((rflags & rwx) == rwx) ? TRUE : FALSE;
 		}
@@ -146,7 +146,10 @@ static bool is_region_valid(const struct sbi_domain_memregion *reg)
 	if (reg->order < 3 || __riscv_xlen < reg->order)
 		return FALSE;
 
-	if (reg->base & (BIT(reg->order) - 1))
+	if (reg->order == __riscv_xlen && reg->base != 0)
+		return FALSE;
+
+	if (reg->order < __riscv_xlen && (reg->base & (BIT(reg->order) - 1)))
 		return FALSE;
 
 	return TRUE;
@@ -522,6 +525,33 @@ int sbi_domain_root_add_memregion(const struct sbi_domain_memregion *reg)
 	return 0;
 }
 
+int sbi_domain_root_add_memrange(unsigned long addr, unsigned long size,
+			   unsigned long align, unsigned long region_flags)
+{
+	int rc;
+	unsigned long pos, end, rsize;
+	struct sbi_domain_memregion reg;
+
+	pos = addr;
+	end = addr + size;
+	while (pos < end) {
+		rsize = pos & (align - 1);
+		if (rsize)
+			rsize = 1UL << sbi_ffs(pos);
+		else
+			rsize = ((end - pos) < align) ?
+				(end - pos) : align;
+
+		sbi_domain_memregion_init(pos, rsize, region_flags, &reg);
+		rc = sbi_domain_root_add_memregion(&reg);
+		if (rc)
+			return rc;
+		pos += rsize;
+	}
+
+	return 0;
+}
+
 int sbi_domain_finalize(struct sbi_scratch *scratch, u32 cold_hartid)
 {
 	int rc;
diff --git a/lib/sbi/sbi_ecall.c b/lib/sbi/sbi_ecall.c
index ce021eb..25dd78c 100644
--- a/lib/sbi/sbi_ecall.c
+++ b/lib/sbi/sbi_ecall.c
@@ -13,6 +13,9 @@
 #include <sbi/sbi_error.h>
 #include <sbi/sbi_trap.h>
 
+extern struct sbi_ecall_extension *sbi_ecall_exts[];
+extern unsigned long sbi_ecall_exts_size;
+
 u16 sbi_ecall_version_major(void)
 {
 	return SBI_ECALL_VERSION_MAJOR;
@@ -144,35 +147,15 @@ int sbi_ecall_handler(struct sbi_trap_regs *regs)
 int sbi_ecall_init(void)
 {
 	int ret;
+	struct sbi_ecall_extension *ext;
+	unsigned long i;
 
-	/* The order of below registrations is performance optimized */
-	ret = sbi_ecall_register_extension(&ecall_time);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_rfence);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_ipi);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_base);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_hsm);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_srst);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_pmu);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_legacy);
-	if (ret)
-		return ret;
-	ret = sbi_ecall_register_extension(&ecall_vendor);
-	if (ret)
-		return ret;
+	for (i = 0; i < sbi_ecall_exts_size; i++) {
+		ext = sbi_ecall_exts[i];
+		ret = sbi_ecall_register_extension(ext);
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
diff --git a/lib/sbi/sbi_ecall_exts.carray b/lib/sbi/sbi_ecall_exts.carray
new file mode 100644
index 0000000..710e3ed
--- /dev/null
+++ b/lib/sbi/sbi_ecall_exts.carray
@@ -0,0 +1,3 @@
+HEADER: sbi/sbi_ecall.h
+TYPE: struct sbi_ecall_extension
+NAME: sbi_ecall_exts
diff --git a/lib/sbi/sbi_ecall_ipi.c b/lib/sbi/sbi_ecall_ipi.c
new file mode 100644
index 0000000..f4797e1
--- /dev/null
+++ b/lib/sbi/sbi_ecall_ipi.c
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ *   Anup Patel <anup.patel@wdc.com>
+ *   Atish Patra <atish.patra@wdc.com>
+ */
+
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_ecall.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_trap.h>
+#include <sbi/sbi_ipi.h>
+
+static int sbi_ecall_ipi_handler(unsigned long extid, unsigned long funcid,
+				 const struct sbi_trap_regs *regs,
+				 unsigned long *out_val,
+				 struct sbi_trap_info *out_trap)
+{
+	int ret = 0;
+
+	if (funcid == SBI_EXT_IPI_SEND_IPI)
+		ret = sbi_ipi_send_smode(regs->a0, regs->a1);
+	else
+		ret = SBI_ENOTSUPP;
+
+	return ret;
+}
+
+struct sbi_ecall_extension ecall_ipi = {
+	.extid_start = SBI_EXT_IPI,
+	.extid_end = SBI_EXT_IPI,
+	.handle = sbi_ecall_ipi_handler,
+};
diff --git a/lib/sbi/sbi_ecall_pmu.c b/lib/sbi/sbi_ecall_pmu.c
index 9ee9e81..826c8a8 100644
--- a/lib/sbi/sbi_ecall_pmu.c
+++ b/lib/sbi/sbi_ecall_pmu.c
@@ -51,7 +51,8 @@ static int sbi_ecall_pmu_handler(unsigned long extid, unsigned long funcid,
 
 		break;
 	case SBI_EXT_PMU_COUNTER_FW_READ:
-		ret = sbi_pmu_ctr_read(regs->a0, out_val);
+		ret = sbi_pmu_ctr_fw_read(regs->a0, &temp);
+		*out_val = temp;
 		break;
 	case SBI_EXT_PMU_COUNTER_START:
 
diff --git a/lib/sbi/sbi_ecall_replace.c b/lib/sbi/sbi_ecall_rfence.c
similarity index 52%
rename from lib/sbi/sbi_ecall_replace.c
rename to lib/sbi/sbi_ecall_rfence.c
index 93f44da..8f0e3d7 100644
--- a/lib/sbi/sbi_ecall_replace.c
+++ b/lib/sbi/sbi_ecall_rfence.c
@@ -9,40 +9,11 @@
  */
 
 #include <sbi/riscv_asm.h>
+#include <sbi/sbi_error.h>
 #include <sbi/sbi_ecall.h>
 #include <sbi/sbi_ecall_interface.h>
-#include <sbi/sbi_error.h>
-#include <sbi/sbi_hart.h>
-#include <sbi/sbi_ipi.h>
-#include <sbi/sbi_system.h>
-#include <sbi/sbi_timer.h>
-#include <sbi/sbi_tlb.h>
 #include <sbi/sbi_trap.h>
-
-static int sbi_ecall_time_handler(unsigned long extid, unsigned long funcid,
-				  const struct sbi_trap_regs *regs,
-				  unsigned long *out_val,
-				  struct sbi_trap_info *out_trap)
-{
-	int ret = 0;
-
-	if (funcid == SBI_EXT_TIME_SET_TIMER) {
-#if __riscv_xlen == 32
-		sbi_timer_event_start((((u64)regs->a1 << 32) | (u64)regs->a0));
-#else
-		sbi_timer_event_start((u64)regs->a0);
-#endif
-	} else
-		ret = SBI_ENOTSUPP;
-
-	return ret;
-}
-
-struct sbi_ecall_extension ecall_time = {
-	.extid_start = SBI_EXT_TIME,
-	.extid_end = SBI_EXT_TIME,
-	.handle = sbi_ecall_time_handler,
-};
+#include <sbi/sbi_tlb.h>
 
 static int sbi_ecall_rfence_handler(unsigned long extid, unsigned long funcid,
 				    const struct sbi_trap_regs *regs,
@@ -113,84 +84,3 @@ struct sbi_ecall_extension ecall_rfence = {
 	.extid_end = SBI_EXT_RFENCE,
 	.handle = sbi_ecall_rfence_handler,
 };
-
-static int sbi_ecall_ipi_handler(unsigned long extid, unsigned long funcid,
-				 const struct sbi_trap_regs *regs,
-				 unsigned long *out_val,
-				 struct sbi_trap_info *out_trap)
-{
-	int ret = 0;
-
-	if (funcid == SBI_EXT_IPI_SEND_IPI)
-		ret = sbi_ipi_send_smode(regs->a0, regs->a1);
-	else
-		ret = SBI_ENOTSUPP;
-
-	return ret;
-}
-
-struct sbi_ecall_extension ecall_ipi = {
-	.extid_start = SBI_EXT_IPI,
-	.extid_end = SBI_EXT_IPI,
-	.handle = sbi_ecall_ipi_handler,
-};
-
-static int sbi_ecall_srst_handler(unsigned long extid, unsigned long funcid,
-				  const struct sbi_trap_regs *regs,
-				  unsigned long *out_val,
-				  struct sbi_trap_info *out_trap)
-{
-	if (funcid == SBI_EXT_SRST_RESET) {
-		if ((((u32)-1U) <= ((u64)regs->a0)) ||
-		    (((u32)-1U) <= ((u64)regs->a1)))
-			return SBI_EINVAL;
-
-		switch (regs->a0) {
-		case SBI_SRST_RESET_TYPE_SHUTDOWN:
-		case SBI_SRST_RESET_TYPE_COLD_REBOOT:
-		case SBI_SRST_RESET_TYPE_WARM_REBOOT:
-			break;
-		default:
-			return SBI_EINVAL;
-		}
-
-		switch (regs->a1) {
-		case SBI_SRST_RESET_REASON_NONE:
-		case SBI_SRST_RESET_REASON_SYSFAIL:
-			break;
-		default:
-			return SBI_EINVAL;
-		}
-
-		if (sbi_system_reset_supported(regs->a0, regs->a1))
-			sbi_system_reset(regs->a0, regs->a1);
-	}
-
-	return SBI_ENOTSUPP;
-}
-
-static int sbi_ecall_srst_probe(unsigned long extid, unsigned long *out_val)
-{
-	u32 type, count = 0;
-
-	/*
-	 * At least one standard reset types should be supported by
-	 * the platform for SBI SRST extension to be usable.
-	 */
-
-	for (type = 0; type <= SBI_SRST_RESET_TYPE_LAST; type++) {
-		if (sbi_system_reset_supported(type,
-					SBI_SRST_RESET_REASON_NONE))
-			count++;
-	}
-
-	*out_val = (count) ? 1 : 0;
-	return 0;
-}
-
-struct sbi_ecall_extension ecall_srst = {
-	.extid_start = SBI_EXT_SRST,
-	.extid_end = SBI_EXT_SRST,
-	.handle = sbi_ecall_srst_handler,
-	.probe = sbi_ecall_srst_probe,
-};
diff --git a/lib/sbi/sbi_ecall_srst.c b/lib/sbi/sbi_ecall_srst.c
new file mode 100644
index 0000000..93b012c
--- /dev/null
+++ b/lib/sbi/sbi_ecall_srst.c
@@ -0,0 +1,75 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ *   Anup Patel <anup.patel@wdc.com>
+ *   Atish Patra <atish.patra@wdc.com>
+ */
+
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_ecall.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_trap.h>
+#include <sbi/sbi_system.h>
+
+static int sbi_ecall_srst_handler(unsigned long extid, unsigned long funcid,
+				  const struct sbi_trap_regs *regs,
+				  unsigned long *out_val,
+				  struct sbi_trap_info *out_trap)
+{
+	if (funcid == SBI_EXT_SRST_RESET) {
+		if ((((u32)-1U) <= ((u64)regs->a0)) ||
+		    (((u32)-1U) <= ((u64)regs->a1)))
+			return SBI_EINVAL;
+
+		switch (regs->a0) {
+		case SBI_SRST_RESET_TYPE_SHUTDOWN:
+		case SBI_SRST_RESET_TYPE_COLD_REBOOT:
+		case SBI_SRST_RESET_TYPE_WARM_REBOOT:
+			break;
+		default:
+			return SBI_EINVAL;
+		}
+
+		switch (regs->a1) {
+		case SBI_SRST_RESET_REASON_NONE:
+		case SBI_SRST_RESET_REASON_SYSFAIL:
+			break;
+		default:
+			return SBI_EINVAL;
+		}
+
+		if (sbi_system_reset_supported(regs->a0, regs->a1))
+			sbi_system_reset(regs->a0, regs->a1);
+	}
+
+	return SBI_ENOTSUPP;
+}
+
+static int sbi_ecall_srst_probe(unsigned long extid, unsigned long *out_val)
+{
+	u32 type, count = 0;
+
+	/*
+	 * At least one standard reset types should be supported by
+	 * the platform for SBI SRST extension to be usable.
+	 */
+
+	for (type = 0; type <= SBI_SRST_RESET_TYPE_LAST; type++) {
+		if (sbi_system_reset_supported(type,
+					SBI_SRST_RESET_REASON_NONE))
+			count++;
+	}
+
+	*out_val = (count) ? 1 : 0;
+	return 0;
+}
+
+struct sbi_ecall_extension ecall_srst = {
+	.extid_start = SBI_EXT_SRST,
+	.extid_end = SBI_EXT_SRST,
+	.handle = sbi_ecall_srst_handler,
+	.probe = sbi_ecall_srst_probe,
+};
diff --git a/lib/sbi/sbi_ecall_time.c b/lib/sbi/sbi_ecall_time.c
new file mode 100644
index 0000000..668cb17
--- /dev/null
+++ b/lib/sbi/sbi_ecall_time.c
@@ -0,0 +1,40 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ *   Anup Patel <anup.patel@wdc.com>
+ *   Atish Patra <atish.patra@wdc.com>
+ */
+
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_ecall.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_trap.h>
+#include <sbi/sbi_timer.h>
+
+static int sbi_ecall_time_handler(unsigned long extid, unsigned long funcid,
+				  const struct sbi_trap_regs *regs,
+				  unsigned long *out_val,
+				  struct sbi_trap_info *out_trap)
+{
+	int ret = 0;
+
+	if (funcid == SBI_EXT_TIME_SET_TIMER) {
+#if __riscv_xlen == 32
+		sbi_timer_event_start((((u64)regs->a1 << 32) | (u64)regs->a0));
+#else
+		sbi_timer_event_start((u64)regs->a0);
+#endif
+	} else
+		ret = SBI_ENOTSUPP;
+
+	return ret;
+}
+
+struct sbi_ecall_extension ecall_time = {
+	.extid_start = SBI_EXT_TIME,
+	.extid_end = SBI_EXT_TIME,
+	.handle = sbi_ecall_time_handler,
+};
diff --git a/lib/sbi/sbi_expected_trap.S b/lib/sbi/sbi_expected_trap.S
index 24891c7..1f2d6b9 100644
--- a/lib/sbi/sbi_expected_trap.S
+++ b/lib/sbi/sbi_expected_trap.S
@@ -11,7 +11,7 @@
 #include <sbi/sbi_trap.h>
 
 	/*
-	 * We assume that faulting instruction is is 4-byte long and blindly
+	 * We assume that faulting instruction is 4-byte long and blindly
 	 * increment SEPC by 4.
 	 *
 	 * The trap info will be saved as follows:
@@ -22,7 +22,7 @@
 	.align 3
 	.global __sbi_expected_trap
 __sbi_expected_trap:
-	/* Without H-extension so, MTVAL2 and MTINST CSRs not available */
+	/* Without H-extension so, MTVAL2 and MTINST CSRs and GVA not available */
 	csrr	a4, CSR_MEPC
 	REG_S	a4, SBI_TRAP_INFO_OFFSET(epc)(a3)
 	csrr	a4, CSR_MCAUSE
@@ -31,6 +31,7 @@ __sbi_expected_trap:
 	REG_S	a4, SBI_TRAP_INFO_OFFSET(tval)(a3)
 	REG_S	zero, SBI_TRAP_INFO_OFFSET(tval2)(a3)
 	REG_S	zero, SBI_TRAP_INFO_OFFSET(tinst)(a3)
+	REG_S	zero, SBI_TRAP_INFO_OFFSET(gva)(a3)
 	csrr	a4, CSR_MEPC
 	addi	a4, a4, 4
 	csrw	CSR_MEPC, a4
@@ -39,7 +40,7 @@ __sbi_expected_trap:
 	.align 3
 	.global __sbi_expected_trap_hext
 __sbi_expected_trap_hext:
-	/* With H-extension so, MTVAL2 and MTINST CSRs available */
+	/* With H-extension so, MTVAL2 and MTINST CSRs and GVA available */
 	csrr	a4, CSR_MEPC
 	REG_S	a4, SBI_TRAP_INFO_OFFSET(epc)(a3)
 	csrr	a4, CSR_MCAUSE
@@ -50,6 +51,18 @@ __sbi_expected_trap_hext:
 	REG_S	a4, SBI_TRAP_INFO_OFFSET(tval2)(a3)
 	csrr	a4, CSR_MTINST
 	REG_S	a4, SBI_TRAP_INFO_OFFSET(tinst)(a3)
+
+	/* Extract GVA bit from MSTATUS or MSTATUSH */
+#if __riscv_xlen == 32
+	csrr	a4, CSR_MSTATUSH
+	srli	a4, a4, MSTATUSH_GVA_SHIFT
+#else
+	csrr	a4, CSR_MSTATUS
+	srli	a4, a4, MSTATUS_GVA_SHIFT
+#endif
+	andi	a4, a4, 1
+	REG_S	a4, SBI_TRAP_INFO_OFFSET(gva)(a3)
+
 	csrr	a4, CSR_MEPC
 	addi	a4, a4, 4
 	csrw	CSR_MEPC, a4
diff --git a/lib/sbi/sbi_hart.c b/lib/sbi/sbi_hart.c
index de86fee..5447c52 100644
--- a/lib/sbi/sbi_hart.c
+++ b/lib/sbi/sbi_hart.c
@@ -19,24 +19,16 @@
 #include <sbi/sbi_hart.h>
 #include <sbi/sbi_math.h>
 #include <sbi/sbi_platform.h>
+#include <sbi/sbi_pmu.h>
 #include <sbi/sbi_string.h>
 #include <sbi/sbi_trap.h>
+#include <sbi/sbi_hfence.h>
 
 extern void __sbi_expected_trap(void);
 extern void __sbi_expected_trap_hext(void);
 
 void (*sbi_hart_expected_trap)(void) = &__sbi_expected_trap;
 
-struct hart_features {
-	bool detected;
-	int priv_version;
-	unsigned long extensions;
-	unsigned int pmp_count;
-	unsigned int pmp_addr_bits;
-	unsigned long pmp_gran;
-	unsigned int mhpm_count;
-	unsigned int mhpm_bits;
-};
 static unsigned long hart_features_offset;
 
 static void mstatus_init(struct sbi_scratch *scratch)
@@ -100,7 +92,7 @@ static void mstatus_init(struct sbi_scratch *scratch)
 		mstateen_val |= SMSTATEEN_STATEN;
 		mstateen_val |= SMSTATEEN0_HSENVCFG;
 
-		if (sbi_hart_has_extension(scratch, SBI_HART_EXT_AIA))
+		if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SMAIA))
 			mstateen_val |= (SMSTATEEN0_AIA | SMSTATEEN0_SVSLCT |
 					SMSTATEEN0_IMSIC);
 		else
@@ -208,8 +200,7 @@ static int delegate_traps(struct sbi_scratch *scratch)
 
 	/* Send M-mode interrupts and most exceptions to S-mode */
 	interrupts = MIP_SSIP | MIP_STIP | MIP_SEIP;
-	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SSCOFPMF))
-		interrupts |= MIP_LCOFIP;
+	interrupts |= sbi_pmu_irq_bit();
 
 	exceptions = (1U << CAUSE_MISALIGNED_FETCH) | (1U << CAUSE_BREAKPOINT) |
 		     (1U << CAUSE_USER_ECALL);
@@ -254,7 +245,7 @@ void sbi_hart_delegation_dump(struct sbi_scratch *scratch,
 
 unsigned int sbi_hart_mhpm_count(struct sbi_scratch *scratch)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	return hfeatures->mhpm_count;
@@ -262,7 +253,7 @@ unsigned int sbi_hart_mhpm_count(struct sbi_scratch *scratch)
 
 unsigned int sbi_hart_pmp_count(struct sbi_scratch *scratch)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	return hfeatures->pmp_count;
@@ -270,7 +261,7 @@ unsigned int sbi_hart_pmp_count(struct sbi_scratch *scratch)
 
 unsigned long sbi_hart_pmp_granularity(struct sbi_scratch *scratch)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	return hfeatures->pmp_gran;
@@ -278,7 +269,7 @@ unsigned long sbi_hart_pmp_granularity(struct sbi_scratch *scratch)
 
 unsigned int sbi_hart_pmp_addrbits(struct sbi_scratch *scratch)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	return hfeatures->pmp_addr_bits;
@@ -286,7 +277,7 @@ unsigned int sbi_hart_pmp_addrbits(struct sbi_scratch *scratch)
 
 unsigned int sbi_hart_mhpm_bits(struct sbi_scratch *scratch)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	return hfeatures->mhpm_bits;
@@ -331,12 +322,33 @@ int sbi_hart_pmp_configure(struct sbi_scratch *scratch)
 		}
 	}
 
+	/*
+	 * As per section 3.7.2 of privileged specification v1.12,
+	 * virtual address translations can be speculatively performed
+	 * (even before actual access). These, along with PMP traslations,
+	 * can be cached. This can pose a problem with CPU hotplug
+	 * and non-retentive suspend scenario because PMP states are
+	 * not preserved.
+	 * It is advisable to flush the caching structures under such
+	 * conditions.
+	 */
+	if (misa_extension('S')) {
+		__asm__ __volatile__("sfence.vma");
+
+		/*
+		 * If hypervisor mode is supported, flush caching
+		 * structures in guest mode too.
+		 */
+		if (misa_extension('H'))
+			__sbi_hfence_gvma_all();
+	}
+
 	return 0;
 }
 
 int sbi_hart_priv_version(struct sbi_scratch *scratch)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	return hfeatures->priv_version;
@@ -346,7 +358,7 @@ void sbi_hart_get_priv_version_str(struct sbi_scratch *scratch,
 				   char *version_str, int nvstr)
 {
 	char *temp;
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	switch (hfeatures->priv_version) {
@@ -368,7 +380,7 @@ void sbi_hart_get_priv_version_str(struct sbi_scratch *scratch,
 }
 
 static inline void __sbi_hart_update_extension(
-					struct hart_features *hfeatures,
+					struct sbi_hart_features *hfeatures,
 					enum sbi_hart_extensions ext,
 					bool enable)
 {
@@ -389,7 +401,7 @@ void sbi_hart_update_extension(struct sbi_scratch *scratch,
 			       enum sbi_hart_extensions ext,
 			       bool enable)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	__sbi_hart_update_extension(hfeatures, ext, enable);
@@ -405,7 +417,7 @@ void sbi_hart_update_extension(struct sbi_scratch *scratch,
 bool sbi_hart_has_extension(struct sbi_scratch *scratch,
 			    enum sbi_hart_extensions ext)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 
 	if (hfeatures->extensions & BIT(ext))
@@ -425,8 +437,8 @@ static inline char *sbi_hart_extension_id2string(int ext)
 	case SBI_HART_EXT_TIME:
 		estr = "time";
 		break;
-	case SBI_HART_EXT_AIA:
-		estr = "aia";
+	case SBI_HART_EXT_SMAIA:
+		estr = "smaia";
 		break;
 	case SBI_HART_EXT_SSTC:
 		estr = "sstc";
@@ -453,7 +465,7 @@ static inline char *sbi_hart_extension_id2string(int ext)
 void sbi_hart_get_extensions_str(struct sbi_scratch *scratch,
 				 char *extensions_str, int nestr)
 {
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 			sbi_scratch_offset_ptr(scratch, hart_features_offset);
 	int offset = 0, ext = 0;
 	char *temp;
@@ -539,7 +551,7 @@ static int hart_pmu_get_allowed_bits(void)
 static int hart_detect_features(struct sbi_scratch *scratch)
 {
 	struct sbi_trap_info trap = {0};
-	struct hart_features *hfeatures =
+	struct sbi_hart_features *hfeatures =
 		sbi_scratch_offset_ptr(scratch, hart_features_offset);
 	unsigned long val, oldval;
 	int rc;
@@ -663,7 +675,7 @@ __mhpm_skip:
 	csr_read_allowed(CSR_MTOPI, (unsigned long)&trap);
 	if (!trap.cause)
 		__sbi_hart_update_extension(hfeatures,
-					SBI_HART_EXT_AIA, true);
+					SBI_HART_EXT_SMAIA, true);
 
 	/* Detect if hart supports stimecmp CSR(Sstc extension) */
 	if (hfeatures->priv_version >= SBI_HART_PRIV_VER_1_12) {
@@ -682,7 +694,8 @@ __mhpm_skip:
 	}
 
 	/* Let platform populate extensions */
-	rc = sbi_platform_extensions_init(sbi_platform_thishart_ptr());
+	rc = sbi_platform_extensions_init(sbi_platform_thishart_ptr(),
+					  hfeatures);
 	if (rc)
 		return rc;
 
@@ -718,7 +731,7 @@ int sbi_hart_init(struct sbi_scratch *scratch, bool cold_boot)
 			sbi_hart_expected_trap = &__sbi_expected_trap_hext;
 
 		hart_features_offset = sbi_scratch_alloc_offset(
-						sizeof(struct hart_features));
+					sizeof(struct sbi_hart_features));
 		if (!hart_features_offset)
 			return SBI_ENOMEM;
 	}
@@ -769,19 +782,12 @@ sbi_hart_switch_mode(unsigned long arg0, unsigned long arg1,
 #if __riscv_xlen == 32
 	if (misa_extension('H')) {
 		valH = csr_read(CSR_MSTATUSH);
-		if (next_virt)
-			valH = INSERT_FIELD(valH, MSTATUSH_MPV, 1);
-		else
-			valH = INSERT_FIELD(valH, MSTATUSH_MPV, 0);
+		valH = INSERT_FIELD(valH, MSTATUSH_MPV, next_virt);
 		csr_write(CSR_MSTATUSH, valH);
 	}
 #else
-	if (misa_extension('H')) {
-		if (next_virt)
-			val = INSERT_FIELD(val, MSTATUS_MPV, 1);
-		else
-			val = INSERT_FIELD(val, MSTATUS_MPV, 0);
-	}
+	if (misa_extension('H'))
+		val = INSERT_FIELD(val, MSTATUS_MPV, next_virt);
 #endif
 	csr_write(CSR_MSTATUS, val);
 	csr_write(CSR_MEPC, next_addr);
diff --git a/lib/sbi/sbi_hsm.c b/lib/sbi/sbi_hsm.c
index 1165acc..836008f 100644
--- a/lib/sbi/sbi_hsm.c
+++ b/lib/sbi/sbi_hsm.c
@@ -289,7 +289,9 @@ int sbi_hsm_hart_start(struct sbi_scratch *scratch,
 	   (hsm_device_has_hart_secondary_boot() && !init_count)) {
 		return hsm_device_hart_start(hartid, scratch->warmboot_addr);
 	} else {
-		sbi_ipi_raw_send(hartid);
+		int rc = sbi_ipi_raw_send(hartid);
+		if (rc)
+		    return rc;
 	}
 
 	return 0;
diff --git a/lib/sbi/sbi_illegal_insn.c b/lib/sbi/sbi_illegal_insn.c
index 84c04f8..9691bce 100644
--- a/lib/sbi/sbi_illegal_insn.c
+++ b/lib/sbi/sbi_illegal_insn.c
@@ -30,6 +30,7 @@ static int truly_illegal_insn(ulong insn, struct sbi_trap_regs *regs)
 	trap.tval = insn;
 	trap.tval2 = 0;
 	trap.tinst = 0;
+	trap.gva   = 0;
 
 	return sbi_trap_redirect(regs, &trap);
 }
@@ -39,6 +40,7 @@ static int misc_mem_opcode_insn(ulong insn, struct sbi_trap_regs *regs)
 	/* Errata workaround: emulate `fence.tso` as `fence rw, rw`. */
 	if ((insn & INSN_MASK_FENCE_TSO) == INSN_MATCH_FENCE_TSO) {
 		smp_mb();
+		regs->mepc += 4;
 		return 0;
 	}
 
diff --git a/lib/sbi/sbi_init.c b/lib/sbi/sbi_init.c
index d57efa7..a8500e5 100644
--- a/lib/sbi/sbi_init.c
+++ b/lib/sbi/sbi_init.c
@@ -63,6 +63,7 @@ static void sbi_boot_print_banner(struct sbi_scratch *scratch)
 static void sbi_boot_print_general(struct sbi_scratch *scratch)
 {
 	char str[128];
+	const struct sbi_pmu_device *pdev;
 	const struct sbi_hsm_device *hdev;
 	const struct sbi_ipi_device *idev;
 	const struct sbi_timer_device *tdev;
@@ -93,6 +94,9 @@ static void sbi_boot_print_general(struct sbi_scratch *scratch)
 	hdev = sbi_hsm_get_device();
 	sbi_printf("Platform HSM Device       : %s\n",
 		   (hdev) ? hdev->name : "---");
+	pdev = sbi_pmu_get_device();
+	sbi_printf("Platform PMU Device       : %s\n",
+		   (pdev) ? pdev->name : "---");
 	srdev = sbi_system_reset_get_device(SBI_SRST_RESET_TYPE_COLD_REBOOT, 0);
 	sbi_printf("Platform Reboot Device    : %s\n",
 		   (srdev) ? srdev->name : "---");
diff --git a/lib/sbi/sbi_ipi.c b/lib/sbi/sbi_ipi.c
index 1014909..7aafbbd 100644
--- a/lib/sbi/sbi_ipi.c
+++ b/lib/sbi/sbi_ipi.c
@@ -208,10 +208,13 @@ skip:
 	};
 }
 
-void sbi_ipi_raw_send(u32 target_hart)
+int sbi_ipi_raw_send(u32 target_hart)
 {
-	if (ipi_dev && ipi_dev->ipi_send)
-		ipi_dev->ipi_send(target_hart);
+	if (!ipi_dev || !ipi_dev->ipi_send)
+		return SBI_EINVAL;
+
+	ipi_dev->ipi_send(target_hart);
+	return 0;
 }
 
 const struct sbi_ipi_device *sbi_ipi_get_device(void)
diff --git a/lib/sbi/sbi_misaligned_ldst.c b/lib/sbi/sbi_misaligned_ldst.c
index fd11798..4b91e07 100644
--- a/lib/sbi/sbi_misaligned_ldst.c
+++ b/lib/sbi/sbi_misaligned_ldst.c
@@ -129,6 +129,7 @@ int sbi_misaligned_load_handler(ulong addr, ulong tval2, ulong tinst,
 		uptrap.tval = addr;
 		uptrap.tval2 = tval2;
 		uptrap.tinst = tinst;
+		uptrap.gva   = sbi_regs_gva(regs);
 		return sbi_trap_redirect(regs, &uptrap);
 	}
 
@@ -244,6 +245,7 @@ int sbi_misaligned_store_handler(ulong addr, ulong tval2, ulong tinst,
 		uptrap.tval = addr;
 		uptrap.tval2 = tval2;
 		uptrap.tinst = tinst;
+		uptrap.gva   = sbi_regs_gva(regs);
 		return sbi_trap_redirect(regs, &uptrap);
 	}
 
diff --git a/lib/sbi/sbi_pmu.c b/lib/sbi/sbi_pmu.c
index 3f5fd10..91d9ccc 100644
--- a/lib/sbi/sbi_pmu.c
+++ b/lib/sbi/sbi_pmu.c
@@ -10,7 +10,9 @@
 #include <sbi/riscv_asm.h>
 #include <sbi/sbi_bitops.h>
 #include <sbi/sbi_console.h>
+#include <sbi/sbi_ecall_interface.h>
 #include <sbi/sbi_hart.h>
+#include <sbi/sbi_hartmask.h>
 #include <sbi/sbi_platform.h>
 #include <sbi/sbi_pmu.h>
 #include <sbi/sbi_scratch.h>
@@ -33,18 +35,6 @@ struct sbi_pmu_hw_event {
 	uint64_t select_mask;
 };
 
-/** Representation of a firmware event */
-struct sbi_pmu_fw_event {
-	/* Event associated with the particular counter */
-	uint32_t event_idx;
-
-	/* Current value of the counter */
-	unsigned long curr_count;
-
-	/* A flag indicating pmu event monitoring is started */
-	bool bStarted;
-};
-
 /* Information about PMU counters as per SBI specification */
 union sbi_pmu_ctr_info {
 	unsigned long value;
@@ -60,14 +50,23 @@ union sbi_pmu_ctr_info {
 	};
 };
 
+/* Platform specific PMU device */
+static const struct sbi_pmu_device *pmu_dev = NULL;
+
 /* Mapping between event range and possible counters  */
 static struct sbi_pmu_hw_event hw_event_map[SBI_PMU_HW_EVENT_MAX] = {0};
 
 /* counter to enabled event mapping */
 static uint32_t active_events[SBI_HARTMASK_MAX_BITS][SBI_PMU_HW_CTR_MAX + SBI_PMU_FW_CTR_MAX];
 
-/* Contains all the information about firmwares events */
-static struct sbi_pmu_fw_event fw_event_map[SBI_HARTMASK_MAX_BITS][SBI_PMU_FW_EVENT_MAX] = {0};
+/* Bitmap of firmware counters started on each HART */
+#if SBI_PMU_FW_CTR_MAX >= BITS_PER_LONG
+#error "Can't handle firmware counters beyond BITS_PER_LONG"
+#endif
+static unsigned long fw_counters_started[SBI_HARTMASK_MAX_BITS];
+
+/* Values of firmwares counters on each HART */
+static uint64_t fw_counters_value[SBI_HARTMASK_MAX_BITS][SBI_PMU_FW_CTR_MAX] = {0};
 
 /* Maximum number of hardware events available */
 static uint32_t num_hw_events;
@@ -107,19 +106,66 @@ static bool pmu_event_select_overlap(struct sbi_pmu_hw_event *evt,
 	return FALSE;
 }
 
+static int pmu_event_validate(unsigned long event_idx)
+{
+	uint32_t event_idx_type = get_cidx_type(event_idx);
+	uint32_t event_idx_code = get_cidx_code(event_idx);
+	uint32_t event_idx_code_max = -1;
+	uint32_t cache_ops_result, cache_ops_id, cache_id;
+
+	switch(event_idx_type) {
+	case SBI_PMU_EVENT_TYPE_HW:
+		event_idx_code_max = SBI_PMU_HW_GENERAL_MAX;
+		break;
+	case SBI_PMU_EVENT_TYPE_FW:
+		if (SBI_PMU_FW_MAX <= event_idx_code &&
+		    pmu_dev && pmu_dev->fw_event_validate_code)
+			return pmu_dev->fw_event_validate_code(event_idx_code);
+		else
+			event_idx_code_max = SBI_PMU_FW_MAX;
+		break;
+	case SBI_PMU_EVENT_TYPE_HW_CACHE:
+		cache_ops_result = event_idx_code &
+					SBI_PMU_EVENT_HW_CACHE_OPS_RESULT;
+		cache_ops_id = (event_idx_code &
+				SBI_PMU_EVENT_HW_CACHE_OPS_ID_MASK) >>
+				SBI_PMU_EVENT_HW_CACHE_OPS_ID_OFFSET;
+		cache_id = (event_idx_code &
+			    SBI_PMU_EVENT_HW_CACHE_ID_MASK) >>
+			    SBI_PMU_EVENT_HW_CACHE_ID_OFFSET;
+		if ((cache_ops_result < SBI_PMU_HW_CACHE_RESULT_MAX) &&
+		    (cache_ops_id < SBI_PMU_HW_CACHE_OP_MAX) &&
+		    (cache_id < SBI_PMU_HW_CACHE_MAX))
+			return event_idx_type;
+		else
+			return SBI_EINVAL;
+		break;
+	case SBI_PMU_EVENT_TYPE_HW_RAW:
+		event_idx_code_max = 1; // event_idx.code should be zero
+		break;
+	default:
+		return SBI_EINVAL;
+	}
+
+	if (event_idx_code < event_idx_code_max)
+		return event_idx_type;
+
+	return SBI_EINVAL;
+}
+
 static int pmu_ctr_validate(uint32_t cidx, uint32_t *event_idx_code)
 {
 	uint32_t event_idx_val;
 	uint32_t event_idx_type;
 	u32 hartid = current_hartid();
 
-	event_idx_val = active_events[hartid][cidx];
-
-	if (cidx >= total_ctrs || (event_idx_val == SBI_PMU_EVENT_IDX_INVALID))
+	if (cidx >= total_ctrs)
 		return SBI_EINVAL;
 
+	event_idx_val = active_events[hartid][cidx];
 	event_idx_type = get_cidx_type(event_idx_val);
-	if (event_idx_type >= SBI_PMU_EVENT_TYPE_MAX)
+	if (event_idx_val == SBI_PMU_EVENT_IDX_INVALID ||
+	    event_idx_type >= SBI_PMU_EVENT_TYPE_MAX)
 		return SBI_EINVAL;
 
 	*event_idx_code = get_cidx_code(event_idx_val);
@@ -127,50 +173,22 @@ static int pmu_ctr_validate(uint32_t cidx, uint32_t *event_idx_code)
 	return event_idx_type;
 }
 
-static int pmu_ctr_read_fw(uint32_t cidx, unsigned long *cval,
-			       uint32_t fw_evt_code)
-{
-	u32 hartid = current_hartid();
-	struct sbi_pmu_fw_event fevent;
-
-	fevent = fw_event_map[hartid][fw_evt_code];
-	*cval = fevent.curr_count;
-
-	return 0;
-}
-
-/* Add a hardware counter read for completeness for future purpose */
-static int pmu_ctr_read_hw(uint32_t cidx, uint64_t *cval)
-{
-	/* Check for invalid hw counter read requests */
-	if (unlikely(cidx == 1))
-		return SBI_EINVAL;
-#if __riscv_xlen == 32
-	uint32_t temp, temph = 0;
-
-	temp = csr_read_num(CSR_MCYCLE + cidx);
-	temph = csr_read_num(CSR_MCYCLEH + cidx);
-	*cval = ((uint64_t)temph << 32) | temp;
-#else
-	*cval = csr_read_num(CSR_MCYCLE + cidx);
-#endif
-
-	return 0;
-}
-
-int sbi_pmu_ctr_read(uint32_t cidx, unsigned long *cval)
+int sbi_pmu_ctr_fw_read(uint32_t cidx, uint64_t *cval)
 {
 	int event_idx_type;
 	uint32_t event_code;
-	uint64_t cval64;
+	u32 hartid = current_hartid();
 
 	event_idx_type = pmu_ctr_validate(cidx, &event_code);
-	if (event_idx_type < 0)
+	if (event_idx_type != SBI_PMU_EVENT_TYPE_FW)
 		return SBI_EINVAL;
-	else if (event_idx_type == SBI_PMU_EVENT_TYPE_FW)
-		pmu_ctr_read_fw(cidx, cval, event_code);
-	else
-		pmu_ctr_read_hw(cidx, &cval64);
+
+	if (SBI_PMU_FW_MAX <= event_code &&
+	    pmu_dev && pmu_dev->fw_counter_read_value)
+		fw_counters_value[hartid][cidx - num_hw_ctrs] =
+			pmu_dev->fw_counter_read_value(cidx - num_hw_ctrs);
+
+	*cval = fw_counters_value[hartid][cidx - num_hw_ctrs];
 
 	return 0;
 }
@@ -315,6 +333,8 @@ static int pmu_ctr_start_hw(uint32_t cidx, uint64_t ival, bool ival_update)
 
 	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SSCOFPMF))
 		pmu_ctr_enable_irq_hw(cidx);
+	if (pmu_dev && pmu_dev->hw_counter_enable_irq)
+		pmu_dev->hw_counter_enable_irq(cidx);
 	csr_write(CSR_MCOUNTINHIBIT, mctr_inhbt);
 
 skip_inhibit_update:
@@ -324,16 +344,36 @@ skip_inhibit_update:
 	return 0;
 }
 
-static int pmu_ctr_start_fw(uint32_t cidx, uint32_t fw_evt_code,
+int sbi_pmu_irq_bit(void)
+{
+	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+
+	if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SSCOFPMF))
+		return MIP_LCOFIP;
+	if (pmu_dev && pmu_dev->hw_counter_irq_bit)
+		return pmu_dev->hw_counter_irq_bit();
+
+	return 0;
+}
+
+static int pmu_ctr_start_fw(uint32_t cidx, uint32_t event_code,
 			    uint64_t ival, bool ival_update)
 {
+	int ret;
 	u32 hartid = current_hartid();
-	struct sbi_pmu_fw_event *fevent;
 
-	fevent = &fw_event_map[hartid][fw_evt_code];
+	if (SBI_PMU_FW_MAX <= event_code &&
+	    pmu_dev && pmu_dev->fw_counter_start) {
+		ret = pmu_dev->fw_counter_start(cidx - num_hw_ctrs,
+						event_code,
+						ival, ival_update);
+		if (ret)
+			return ret;
+	}
+
 	if (ival_update)
-		fevent->curr_count = ival;
-	fevent->bStarted = TRUE;
+		fw_counters_value[hartid][cidx - num_hw_ctrs] = ival;
+	fw_counters_started[hartid] |= BIT(cidx - num_hw_ctrs);
 
 	return 0;
 }
@@ -343,25 +383,26 @@ int sbi_pmu_ctr_start(unsigned long cbase, unsigned long cmask,
 {
 	int event_idx_type;
 	uint32_t event_code;
-	unsigned long ctr_mask = cmask << cbase;
 	int ret = SBI_EINVAL;
 	bool bUpdate = FALSE;
+	int i, cidx;
 
-	if (sbi_fls(ctr_mask) >= total_ctrs)
+	if ((cbase + sbi_fls(cmask)) >= total_ctrs)
 		return ret;
 
 	if (flags & SBI_PMU_START_FLAG_SET_INIT_VALUE)
 		bUpdate = TRUE;
 
-	for_each_set_bit_from(cbase, &ctr_mask, total_ctrs) {
-		event_idx_type = pmu_ctr_validate(cbase, &event_code);
+	for_each_set_bit(i, &cmask, total_ctrs) {
+		cidx = i + cbase;
+		event_idx_type = pmu_ctr_validate(cidx, &event_code);
 		if (event_idx_type < 0)
 			/* Continue the start operation for other counters */
 			continue;
 		else if (event_idx_type == SBI_PMU_EVENT_TYPE_FW)
-			ret = pmu_ctr_start_fw(cbase, event_code, ival, bUpdate);
+			ret = pmu_ctr_start_fw(cidx, event_code, ival, bUpdate);
 		else
-			ret = pmu_ctr_start_hw(cbase, ival, bUpdate);
+			ret = pmu_ctr_start_hw(cidx, ival, bUpdate);
 	}
 
 	return ret;
@@ -389,11 +430,18 @@ static int pmu_ctr_stop_hw(uint32_t cidx)
 		return SBI_EALREADY_STOPPED;
 }
 
-static int pmu_ctr_stop_fw(uint32_t cidx, uint32_t fw_evt_code)
+static int pmu_ctr_stop_fw(uint32_t cidx, uint32_t event_code)
 {
-	u32 hartid = current_hartid();
+	int ret;
+
+	if (SBI_PMU_FW_MAX <= event_code &&
+	    pmu_dev && pmu_dev->fw_counter_stop) {
+		ret = pmu_dev->fw_counter_stop(cidx - num_hw_ctrs);
+		if (ret)
+			return ret;
+	}
 
-	fw_event_map[hartid][fw_evt_code].bStarted = FALSE;
+	fw_counters_started[current_hartid()] &= ~BIT(cidx - num_hw_ctrs);
 
 	return 0;
 }
@@ -421,25 +469,26 @@ int sbi_pmu_ctr_stop(unsigned long cbase, unsigned long cmask,
 	int ret = SBI_EINVAL;
 	int event_idx_type;
 	uint32_t event_code;
-	unsigned long ctr_mask = cmask << cbase;
+	int i, cidx;
 
-	if (sbi_fls(ctr_mask) >= total_ctrs)
+	if ((cbase + sbi_fls(cmask)) >= total_ctrs)
 		return SBI_EINVAL;
 
-	for_each_set_bit_from(cbase, &ctr_mask, total_ctrs) {
-		event_idx_type = pmu_ctr_validate(cbase, &event_code);
+	for_each_set_bit(i, &cmask, total_ctrs) {
+		cidx = i + cbase;
+		event_idx_type = pmu_ctr_validate(cidx, &event_code);
 		if (event_idx_type < 0)
 			/* Continue the stop operation for other counters */
 			continue;
 
 		else if (event_idx_type == SBI_PMU_EVENT_TYPE_FW)
-			ret = pmu_ctr_stop_fw(cbase, event_code);
+			ret = pmu_ctr_stop_fw(cidx, event_code);
 		else
-			ret = pmu_ctr_stop_hw(cbase);
+			ret = pmu_ctr_stop_hw(cidx);
 
 		if (flag & SBI_PMU_STOP_FLAG_RESET) {
-			active_events[hartid][cbase] = SBI_PMU_EVENT_IDX_INVALID;
-			pmu_reset_hw_mhpmevent(cbase);
+			active_events[hartid][cidx] = SBI_PMU_EVENT_IDX_INVALID;
+			pmu_reset_hw_mhpmevent(cidx);
 		}
 	}
 
@@ -480,6 +529,9 @@ static int pmu_update_hw_mhpmevent(struct sbi_pmu_hw_event *hw_evt, int ctr_idx,
 		mhpmevent_val = (mhpmevent_val & ~MHPMEVENT_SSCOF_MASK) |
 				 MHPMEVENT_MINH | MHPMEVENT_OF;
 
+	if (pmu_dev && pmu_dev->hw_counter_disable_irq)
+		pmu_dev->hw_counter_disable_irq(ctr_idx);
+
 	/* Update the inhibit flags based on inhibit flags received from supervisor */
 	pmu_update_inhibit_flags(flags, &mhpmevent_val);
 
@@ -587,21 +639,26 @@ static int pmu_ctr_find_hw(unsigned long cbase, unsigned long cmask, unsigned lo
  * Thus, select the first available fw counter after sanity
  * check.
  */
-static int pmu_ctr_find_fw(unsigned long cbase, unsigned long cmask, u32 hartid)
+static int pmu_ctr_find_fw(unsigned long cbase, unsigned long cmask,
+			   uint32_t event_code, u32 hartid)
 {
-	int i = 0;
-	int fw_base;
-	unsigned long ctr_mask = cmask << cbase;
+	int i, cidx;
 
-	if (cbase < num_hw_ctrs)
-		fw_base = num_hw_ctrs;
-	else
-		fw_base = cbase;
+	for_each_set_bit(i, &cmask, BITS_PER_LONG) {
+		cidx = i + cbase;
+		if (cidx < num_hw_ctrs || total_ctrs <= cidx)
+			continue;
+		if (active_events[hartid][i] != SBI_PMU_EVENT_IDX_INVALID)
+			continue;
+		if (SBI_PMU_FW_MAX <= event_code &&
+		    pmu_dev && pmu_dev->fw_counter_match_code) {
+			if (!pmu_dev->fw_counter_match_code(cidx - num_hw_ctrs,
+							    event_code))
+				continue;
+		}
 
-	for (i = fw_base; i < total_ctrs; i++)
-		if ((active_events[hartid][i] == SBI_PMU_EVENT_IDX_INVALID) &&
-		    ((1UL << i) & ctr_mask))
-			return i;
+		return i;
+	}
 
 	return SBI_ENOTSUPP;
 }
@@ -610,16 +667,18 @@ int sbi_pmu_ctr_cfg_match(unsigned long cidx_base, unsigned long cidx_mask,
 			  unsigned long flags, unsigned long event_idx,
 			  uint64_t event_data)
 {
-	int ctr_idx = SBI_ENOTSUPP;
-	u32 hartid = current_hartid();
-	int event_type = get_cidx_type(event_idx);
-	struct sbi_pmu_fw_event *fevent;
-	uint32_t fw_evt_code;
-	unsigned long tmp = cidx_mask << cidx_base;
+	int ret, ctr_idx = SBI_ENOTSUPP;
+	u32 event_code, hartid = current_hartid();
+	int event_type;
 
 	/* Do a basic sanity check of counter base & mask */
-	if (sbi_fls(tmp) >= total_ctrs || event_type >= SBI_PMU_EVENT_TYPE_MAX)
+	if ((cidx_base + sbi_fls(cidx_mask)) >= total_ctrs)
+		return SBI_EINVAL;
+
+	event_type = pmu_event_validate(event_idx);
+	if (event_type < 0)
 		return SBI_EINVAL;
+	event_code = get_cidx_code(event_idx);
 
 	if (flags & SBI_PMU_CFG_FLAG_SKIP_MATCH) {
 		/* The caller wants to skip the match because it already knows the
@@ -634,7 +693,7 @@ int sbi_pmu_ctr_cfg_match(unsigned long cidx_base, unsigned long cidx_mask,
 
 	if (event_type == SBI_PMU_EVENT_TYPE_FW) {
 		/* Any firmware counter can be used track any firmware event */
-		ctr_idx = pmu_ctr_find_fw(cidx_base, cidx_mask, hartid);
+		ctr_idx = pmu_ctr_find_fw(cidx_base, cidx_mask, event_code, hartid);
 	} else {
 		ctr_idx = pmu_ctr_find_hw(cidx_base, cidx_mask, flags, event_idx,
 					  event_data);
@@ -651,30 +710,46 @@ skip_match:
 		if (flags & SBI_PMU_CFG_FLAG_AUTO_START)
 			pmu_ctr_start_hw(ctr_idx, 0, false);
 	} else if (event_type == SBI_PMU_EVENT_TYPE_FW) {
-		fw_evt_code = get_cidx_code(event_idx);
-		fevent = &fw_event_map[hartid][fw_evt_code];
 		if (flags & SBI_PMU_CFG_FLAG_CLEAR_VALUE)
-			fevent->curr_count = 0;
-		if (flags & SBI_PMU_CFG_FLAG_AUTO_START)
-			fevent->bStarted = TRUE;
+			fw_counters_value[hartid][ctr_idx - num_hw_ctrs] = 0;
+		if (flags & SBI_PMU_CFG_FLAG_AUTO_START) {
+			if (SBI_PMU_FW_MAX <= event_code &&
+			    pmu_dev && pmu_dev->fw_counter_start) {
+				ret = pmu_dev->fw_counter_start(
+					ctr_idx - num_hw_ctrs, event_code,
+					fw_counters_value[hartid][ctr_idx - num_hw_ctrs],
+					true);
+				if (ret)
+					return ret;
+			}
+			fw_counters_started[hartid] |= BIT(ctr_idx - num_hw_ctrs);
+		}
 	}
 
 	return ctr_idx;
 }
 
-inline int sbi_pmu_ctr_incr_fw(enum sbi_pmu_fw_event_code_id fw_id)
+int sbi_pmu_ctr_incr_fw(enum sbi_pmu_fw_event_code_id fw_id)
 {
-	u32 hartid = current_hartid();
-	struct sbi_pmu_fw_event *fevent;
+	u32 cidx, hartid = current_hartid();
+	uint64_t *fcounter = NULL;
+
+	if (likely(!fw_counters_started[hartid]))
+		return 0;
 
 	if (unlikely(fw_id >= SBI_PMU_FW_MAX))
 		return SBI_EINVAL;
 
-	fevent = &fw_event_map[hartid][fw_id];
+	for (cidx = num_hw_ctrs; cidx < total_ctrs; cidx++) {
+		if (get_cidx_code(active_events[hartid][cidx]) == fw_id &&
+		    (fw_counters_started[hartid] & BIT(cidx - num_hw_ctrs))) {
+			fcounter = &fw_counters_value[hartid][cidx - num_hw_ctrs];
+			break;
+		}
+	}
 
-	/* PMU counters will be only enabled during performance debugging */
-	if (unlikely(fevent->bStarted))
-		fevent->curr_count++;
+	if (fcounter)
+		(*fcounter)++;
 
 	return 0;
 }
@@ -705,8 +780,8 @@ int sbi_pmu_ctr_get_info(uint32_t cidx, unsigned long *ctr_info)
 	} else {
 		/* it's a firmware counter */
 		cinfo.type = SBI_PMU_CTR_TYPE_FW;
-		/* Firmware counters are XLEN bits wide */
-		cinfo.width = BITS_PER_LONG - 1;
+		/* Firmware counters are always 64 bits wide */
+		cinfo.width = 63;
 	}
 
 	*ctr_info = cinfo.value;
@@ -722,8 +797,21 @@ static void pmu_reset_event_map(u32 hartid)
 	for (j = 3; j < total_ctrs; j++)
 		active_events[hartid][j] = SBI_PMU_EVENT_IDX_INVALID;
 	for (j = 0; j < SBI_PMU_FW_CTR_MAX; j++)
-		sbi_memset(&fw_event_map[hartid][j], 0,
-			   sizeof(struct sbi_pmu_fw_event));
+		fw_counters_value[hartid][j] = 0;
+	fw_counters_started[hartid] = 0;
+}
+
+const struct sbi_pmu_device *sbi_pmu_get_device(void)
+{
+	return pmu_dev;
+}
+
+void sbi_pmu_set_device(const struct sbi_pmu_device *dev)
+{
+	if (!dev || pmu_dev)
+		return;
+
+	pmu_dev = dev;
 }
 
 void sbi_pmu_exit(struct sbi_scratch *scratch)
diff --git a/lib/sbi/sbi_string.c b/lib/sbi/sbi_string.c
index c163f31..9ebea69 100644
--- a/lib/sbi/sbi_string.c
+++ b/lib/sbi/sbi_string.c
@@ -59,7 +59,6 @@ size_t sbi_strnlen(const char *str, size_t count)
 	while (*str != '\0' && ret < count) {
 		ret++;
 		str++;
-		count--;
 	}
 
 	return ret;
diff --git a/lib/sbi/sbi_timer.c b/lib/sbi/sbi_timer.c
index b0a6e63..4b24cbe 100644
--- a/lib/sbi/sbi_timer.c
+++ b/lib/sbi/sbi_timer.c
@@ -76,6 +76,19 @@ void sbi_timer_delay_loop(ulong units, u64 unit_freq,
 		delay_fn(opaque);
 }
 
+bool sbi_timer_waitms_until(bool (*predicate)(void *), void *arg,
+			    uint64_t timeout_ms)
+{
+	uint64_t start_time = sbi_timer_value();
+	uint64_t ticks =
+		(sbi_timer_get_device()->timer_freq / 1000) *
+		timeout_ms;
+	while(!predicate(arg))
+		if (sbi_timer_value() - start_time  >= ticks)
+			return false;
+	return true;
+}
+
 u64 sbi_timer_value(void)
 {
 	if (get_time_val)
diff --git a/lib/sbi/sbi_trap.c b/lib/sbi/sbi_trap.c
index ee3e4e9..c875c90 100644
--- a/lib/sbi/sbi_trap.c
+++ b/lib/sbi/sbi_trap.c
@@ -9,6 +9,7 @@
 
 #include <sbi/riscv_asm.h>
 #include <sbi/riscv_encoding.h>
+#include <sbi/sbi_bitops.h>
 #include <sbi/sbi_console.h>
 #include <sbi/sbi_ecall.h>
 #include <sbi/sbi_error.h>
@@ -118,14 +119,18 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,
 	regs->mstatus |= (next_virt) ? MSTATUS_MPV : 0UL;
 #endif
 
-	/* Update HSTATUS for VS/VU-mode to HS-mode transition */
-	if (misa_extension('H') && prev_virt && !next_virt) {
-		/* Update HSTATUS SPVP and SPV bits */
+	/* Update hypervisor CSRs if going to HS-mode */
+	if (misa_extension('H') && !next_virt) {
 		hstatus = csr_read(CSR_HSTATUS);
-		hstatus &= ~HSTATUS_SPVP;
-		hstatus |= (prev_mode == PRV_S) ? HSTATUS_SPVP : 0;
+		if (prev_virt) {
+			/* hstatus.SPVP is only updated if coming from VS/VU-mode */
+			hstatus &= ~HSTATUS_SPVP;
+			hstatus |= (prev_mode == PRV_S) ? HSTATUS_SPVP : 0;
+		}
 		hstatus &= ~HSTATUS_SPV;
 		hstatus |= (prev_virt) ? HSTATUS_SPV : 0;
+		hstatus &= ~HSTATUS_GVA;
+		hstatus |= (trap->gva) ? HSTATUS_GVA : 0;
 		csr_write(CSR_HSTATUS, hstatus);
 		csr_write(CSR_HTVAL, trap->tval2);
 		csr_write(CSR_HTINST, trap->tinst);
@@ -270,7 +275,7 @@ struct sbi_trap_regs *sbi_trap_handler(struct sbi_trap_regs *regs)
 
 	if (mcause & (1UL << (__riscv_xlen - 1))) {
 		if (sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
-					   SBI_HART_EXT_AIA))
+					   SBI_HART_EXT_SMAIA))
 			rc = sbi_trap_aia_irq(regs, mcause);
 		else
 			rc = sbi_trap_nonaia_irq(regs, mcause);
@@ -311,6 +316,8 @@ struct sbi_trap_regs *sbi_trap_handler(struct sbi_trap_regs *regs)
 		trap.tval = mtval;
 		trap.tval2 = mtval2;
 		trap.tinst = mtinst;
+		trap.gva   = sbi_regs_gva(regs);
+
 		rc = sbi_trap_redirect(regs, &trap);
 		break;
 	};
diff --git a/lib/utils/Kconfig b/lib/utils/Kconfig
new file mode 100644
index 0000000..5a71e75
--- /dev/null
+++ b/lib/utils/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "Utils and Drivers Support"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/fdt/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/gpio/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/i2c/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/ipi/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/irqchip/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/libfdt/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/reset/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/serial/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/sys/Kconfig"
+
+source "$(OPENSBI_SRC_DIR)/lib/utils/timer/Kconfig"
+
+endmenu
diff --git a/lib/utils/fdt/Kconfig b/lib/utils/fdt/Kconfig
new file mode 100644
index 0000000..23b003b
--- /dev/null
+++ b/lib/utils/fdt/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menuconfig FDT
+	bool "Flattened Device Tree (FDT) support"
+	select LIBFDT
+	default n
+
+if FDT
+
+config FDT_DOMAIN
+	bool "FDT domain support"
+	default n
+
+config FDT_PMU
+	bool "FDT performance monitoring unit (PMU) support"
+	default n
+
+endif
diff --git a/lib/utils/fdt/fdt_domain.c b/lib/utils/fdt/fdt_domain.c
index bd0eec3..0568603 100644
--- a/lib/utils/fdt/fdt_domain.c
+++ b/lib/utils/fdt/fdt_domain.c
@@ -246,7 +246,7 @@ static int __fdt_parse_region(void *fdt, int domain_offset,
 
 	/* Read "base" DT property */
 	val = fdt_getprop(fdt, region_offset, "base", &len);
-	if (!val && len >= 8)
+	if (!val || len != 8)
 		return SBI_EINVAL;
 	val64 = fdt32_to_cpu(val[0]);
 	val64 = (val64 << 32) | fdt32_to_cpu(val[1]);
@@ -254,7 +254,7 @@ static int __fdt_parse_region(void *fdt, int domain_offset,
 
 	/* Read "order" DT property */
 	val = fdt_getprop(fdt, region_offset, "order", &len);
-	if (!val && len >= 4)
+	if (!val || len != 4)
 		return SBI_EINVAL;
 	val32 = fdt32_to_cpu(*val);
 	if (val32 < 3 || __riscv_xlen < val32)
diff --git a/lib/utils/fdt/fdt_fixup.c b/lib/utils/fdt/fdt_fixup.c
index d1050bb..41f6cbb 100644
--- a/lib/utils/fdt/fdt_fixup.c
+++ b/lib/utils/fdt/fdt_fixup.c
@@ -74,22 +74,23 @@ static void fdt_domain_based_fixup_one(void *fdt, int nodeoff)
 	}
 }
 
-void fdt_aplic_fixup(void *fdt)
+static void fdt_fixup_node(void *fdt, const char *compatible)
 {
 	int noff = 0;
 
 	while ((noff = fdt_node_offset_by_compatible(fdt, noff,
-						     "riscv,aplic")) >= 0)
+						     compatible)) >= 0)
 		fdt_domain_based_fixup_one(fdt, noff);
 }
 
-void fdt_imsic_fixup(void *fdt)
+void fdt_aplic_fixup(void *fdt)
 {
-	int noff = 0;
+	fdt_fixup_node(fdt, "riscv,aplic");
+}
 
-	while ((noff = fdt_node_offset_by_compatible(fdt, noff,
-						     "riscv,imsics")) >= 0)
-		fdt_domain_based_fixup_one(fdt, noff);
+void fdt_imsic_fixup(void *fdt)
+{
+	fdt_fixup_node(fdt, "riscv,imsics");
 }
 
 void fdt_plic_fixup(void *fdt)
diff --git a/lib/utils/fdt/fdt_helper.c b/lib/utils/fdt/fdt_helper.c
index 2bdb1ef..48bc2fe 100644
--- a/lib/utils/fdt/fdt_helper.c
+++ b/lib/utils/fdt/fdt_helper.c
@@ -23,12 +23,11 @@
 #define DEFAULT_UART_REG_IO_WIDTH	1
 #define DEFAULT_UART_REG_OFFSET		0
 
+#define DEFAULT_RENESAS_SCIF_FREQ		100000000
+#define DEFAULT_RENESAS_SCIF_BAUD		115200
+
 #define DEFAULT_SIFIVE_UART_FREQ		0
 #define DEFAULT_SIFIVE_UART_BAUD		115200
-#define DEFAULT_SIFIVE_UART_REG_SHIFT		0
-#define DEFAULT_SIFIVE_UART_REG_IO_WIDTH	4
-
-#define DEFAULT_GAISLER_UART_REG_IO_WIDTH	4
 
 #define DEFAULT_SHAKTI_UART_FREQ		50000000
 #define DEFAULT_SHAKTI_UART_BAUD		115200
@@ -128,7 +127,7 @@ static int fdt_translate_address(void *fdt, uint64_t reg, int parent,
 	int i, rlen;
 	int cell_addr, cell_size;
 	const fdt32_t *ranges;
-	uint64_t offset = 0, caddr = 0, paddr = 0, rsize = 0;
+	uint64_t offset, caddr = 0, paddr = 0, rsize = 0;
 
 	cell_addr = fdt_address_cells(fdt, parent);
 	if (cell_addr < 1)
@@ -314,8 +313,10 @@ int fdt_parse_timebase_frequency(void *fdt, unsigned long *freq)
 	return 0;
 }
 
-int fdt_parse_gaisler_uart_node(void *fdt, int nodeoffset,
-				struct platform_uart_data *uart)
+static int fdt_parse_uart_node_common(void *fdt, int nodeoffset,
+				      struct platform_uart_data *uart,
+				      unsigned long default_freq,
+				      unsigned long default_baud)
 {
 	int len, rc;
 	const fdt32_t *val;
@@ -338,126 +339,60 @@ int fdt_parse_gaisler_uart_node(void *fdt, int nodeoffset,
 	if (len > 0 && val)
 		uart->freq = fdt32_to_cpu(*val);
 	else
-		uart->freq = DEFAULT_UART_FREQ;
+		uart->freq = default_freq;
 
 	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len);
 	if (len > 0 && val)
 		uart->baud = fdt32_to_cpu(*val);
 	else
-		uart->baud = DEFAULT_UART_BAUD;
-
-	/* For Gaisler APBUART, the reg-shift and reg-io-width are fixed .*/
-	uart->reg_shift	   = DEFAULT_UART_REG_SHIFT;
-	uart->reg_io_width = DEFAULT_GAISLER_UART_REG_IO_WIDTH;
+		uart->baud = default_baud;
 
 	return 0;
 }
 
-int fdt_parse_shakti_uart_node(void *fdt, int nodeoffset,
-			       struct platform_uart_data *uart)
+int fdt_parse_gaisler_uart_node(void *fdt, int nodeoffset,
+				struct platform_uart_data *uart)
 {
-	int len, rc;
-	const fdt32_t *val;
-	uint64_t reg_addr, reg_size;
-
-	if (nodeoffset < 0 || !uart || !fdt)
-		return SBI_ENODEV;
-
-	rc = fdt_get_node_addr_size(fdt, nodeoffset, 0,
-				    &reg_addr, &reg_size);
-	if (rc < 0 || !reg_addr || !reg_size)
-		return SBI_ENODEV;
-	uart->addr = reg_addr;
-
-	/**
-	 * UART address is mandaotry. clock-frequency and current-speed
-	 * may not be present. Don't return error.
-	 */
-	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "clock-frequency", &len);
-	if (len > 0 && val)
-		uart->freq = fdt32_to_cpu(*val);
-	else
-		uart->freq = DEFAULT_SHAKTI_UART_FREQ;
+	return fdt_parse_uart_node_common(fdt, nodeoffset, uart,
+					DEFAULT_UART_FREQ,
+					DEFAULT_UART_BAUD);
+}
 
-	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len);
-	if (len > 0 && val)
-		uart->baud = fdt32_to_cpu(*val);
-	else
-		uart->baud = DEFAULT_SHAKTI_UART_BAUD;
+int fdt_parse_renesas_scif_node(void *fdt, int nodeoffset,
+				struct platform_uart_data *uart)
+{
+	return fdt_parse_uart_node_common(fdt, nodeoffset, uart,
+					  DEFAULT_RENESAS_SCIF_FREQ,
+					  DEFAULT_RENESAS_SCIF_BAUD);
+}
 
-	return 0;
+int fdt_parse_shakti_uart_node(void *fdt, int nodeoffset,
+			       struct platform_uart_data *uart)
+{
+	return fdt_parse_uart_node_common(fdt, nodeoffset, uart,
+					DEFAULT_SHAKTI_UART_FREQ,
+					DEFAULT_SHAKTI_UART_BAUD);
 }
 
 int fdt_parse_sifive_uart_node(void *fdt, int nodeoffset,
 			       struct platform_uart_data *uart)
 {
-	int len, rc;
-	const fdt32_t *val;
-	uint64_t reg_addr, reg_size;
-
-	if (nodeoffset < 0 || !uart || !fdt)
-		return SBI_ENODEV;
-
-	rc = fdt_get_node_addr_size(fdt, nodeoffset, 0,
-				    &reg_addr, &reg_size);
-	if (rc < 0 || !reg_addr || !reg_size)
-		return SBI_ENODEV;
-	uart->addr = reg_addr;
-
-	/**
-	 * UART address is mandaotry. clock-frequency and current-speed
-	 * may not be present. Don't return error.
-	 */
-	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "clock-frequency", &len);
-	if (len > 0 && val)
-		uart->freq = fdt32_to_cpu(*val);
-	else
-		uart->freq = DEFAULT_SIFIVE_UART_FREQ;
-
-	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len);
-	if (len > 0 && val)
-		uart->baud = fdt32_to_cpu(*val);
-	else
-		uart->baud = DEFAULT_SIFIVE_UART_BAUD;
-
-	/* For SiFive UART, the reg-shift and reg-io-width are fixed .*/
-	uart->reg_shift = DEFAULT_SIFIVE_UART_REG_SHIFT;
-	uart->reg_io_width = DEFAULT_SIFIVE_UART_REG_IO_WIDTH;
-
-	return 0;
+	return fdt_parse_uart_node_common(fdt, nodeoffset, uart,
+					DEFAULT_SIFIVE_UART_FREQ,
+					DEFAULT_SIFIVE_UART_BAUD);
 }
 
-int fdt_parse_uart8250_node(void *fdt, int nodeoffset,
-			    struct platform_uart_data *uart)
+int fdt_parse_uart_node(void *fdt, int nodeoffset,
+			struct platform_uart_data *uart)
 {
 	int len, rc;
 	const fdt32_t *val;
-	uint64_t reg_addr, reg_size;
-
-	if (nodeoffset < 0 || !uart || !fdt)
-		return SBI_ENODEV;
 
-	rc = fdt_get_node_addr_size(fdt, nodeoffset, 0,
-				    &reg_addr, &reg_size);
-	if (rc < 0 || !reg_addr || !reg_size)
-		return SBI_ENODEV;
-	uart->addr = reg_addr;
-
-	/**
-	 * UART address is mandaotry. clock-frequency and current-speed
-	 * may not be present. Don't return error.
-	 */
-	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "clock-frequency", &len);
-	if (len > 0 && val)
-		uart->freq = fdt32_to_cpu(*val);
-	else
-		uart->freq = DEFAULT_UART_FREQ;
-
-	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "current-speed", &len);
-	if (len > 0 && val)
-		uart->baud = fdt32_to_cpu(*val);
-	else
-		uart->baud = DEFAULT_UART_BAUD;
+	rc = fdt_parse_uart_node_common(fdt, nodeoffset, uart,
+					DEFAULT_UART_FREQ,
+					DEFAULT_UART_BAUD);
+	if (rc)
+		return rc;
 
 	val = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "reg-shift", &len);
 	if (len > 0 && val)
@@ -492,25 +427,13 @@ int fdt_parse_uart8250(void *fdt, struct platform_uart_data *uart,
 	if (nodeoffset < 0)
 		return nodeoffset;
 
-	return fdt_parse_uart8250_node(fdt, nodeoffset, uart);
+	return fdt_parse_uart_node(fdt, nodeoffset, uart);
 }
 
 int fdt_parse_xlnx_uartlite_node(void *fdt, int nodeoffset,
 			       struct platform_uart_data *uart)
 {
-	int rc;
-	uint64_t reg_addr, reg_size;
-
-	if (nodeoffset < 0 || !uart || !fdt)
-		return SBI_ENODEV;
-
-	rc = fdt_get_node_addr_size(fdt, nodeoffset, 0,
-				    &reg_addr, &reg_size);
-	if (rc < 0 || !reg_addr || !reg_size)
-		return SBI_ENODEV;
-	uart->addr = reg_addr;
-
-	return 0;
+	return fdt_parse_uart_node_common(fdt, nodeoffset, uart, 0, 0);
 }
 
 int fdt_parse_aplic_node(void *fdt, int nodeoff, struct aplic_data *aplic)
@@ -860,7 +783,7 @@ int fdt_parse_aclint_node(void *fdt, int nodeoffset, bool for_timer,
 			continue;
 
 		cpu_offset = fdt_parent_offset(fdt, cpu_intc_offset);
-		if (cpu_intc_offset < 0)
+		if (cpu_offset < 0)
 			continue;
 
 		rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
@@ -888,6 +811,118 @@ int fdt_parse_aclint_node(void *fdt, int nodeoffset, bool for_timer,
 	return 0;
 }
 
+int fdt_parse_plmt_node(void *fdt, int nodeoffset, unsigned long *plmt_base,
+			  unsigned long *plmt_size, u32 *hart_count)
+{
+	const fdt32_t *val;
+	int rc, i, count;
+	uint64_t reg_addr, reg_size;
+	u32 phandle, hwirq, hartid, hcount;
+
+	if (nodeoffset < 0 || !fdt || !plmt_base ||
+	    !hart_count || !plmt_size)
+		return SBI_EINVAL;
+
+	rc = fdt_get_node_addr_size(fdt, nodeoffset, 0,
+				    &reg_addr, &reg_size);
+	if (rc < 0)
+		return SBI_ENODEV;
+	*plmt_base = reg_addr;
+	*plmt_size = reg_size;
+
+	val = fdt_getprop(fdt, nodeoffset, "interrupts-extended", &count);
+	if (!val || count < sizeof(fdt32_t))
+		return 0;
+	count = count / sizeof(fdt32_t);
+
+	hcount = 0;
+	for (i = 0; i < (count / 2); i++) {
+		int cpu_offset, cpu_intc_offset;
+
+		phandle = fdt32_to_cpu(val[2 * i]);
+		hwirq = fdt32_to_cpu(val[2 * i + 1]);
+
+		cpu_intc_offset = fdt_node_offset_by_phandle(fdt, phandle);
+		if (cpu_intc_offset < 0)
+			continue;
+
+		cpu_offset = fdt_parent_offset(fdt, cpu_intc_offset);
+		if (cpu_offset < 0)
+			continue;
+
+		rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
+
+		if (rc)
+			continue;
+
+		if (SBI_HARTMASK_MAX_BITS <= hartid)
+			continue;
+
+		if (hwirq == IRQ_M_TIMER)
+			hcount++;
+	}
+
+	*hart_count = hcount;
+
+	return 0;
+}
+
+int fdt_parse_plicsw_node(void *fdt, int nodeoffset, unsigned long *plicsw_base,
+			  unsigned long *size, u32 *hart_count)
+{
+	const fdt32_t *val;
+	int rc, i, count;
+	uint64_t reg_addr, reg_size;
+	u32 phandle, hwirq, hartid, hcount;
+
+	if (nodeoffset < 0 || !fdt || !plicsw_base ||
+	    !hart_count || !size)
+		return SBI_EINVAL;
+
+	rc = fdt_get_node_addr_size(fdt, nodeoffset, 0,
+				    &reg_addr, &reg_size);
+	if (rc < 0)
+		return SBI_ENODEV;
+	*plicsw_base = reg_addr;
+	*size = reg_size;
+
+	val = fdt_getprop(fdt, nodeoffset, "interrupts-extended", &count);
+	if (!val || count < sizeof(fdt32_t))
+		return 0;
+	count = count / sizeof(fdt32_t);
+
+	hcount = 0;
+	for (i = 0; i < (count / 2); i++) {
+		int cpu_offset, cpu_intc_offset;
+
+		phandle = fdt32_to_cpu(val[2 * i]);
+		hwirq = fdt32_to_cpu(val[2 * i + 1]);
+
+		cpu_intc_offset = fdt_node_offset_by_phandle(fdt, phandle);
+		if (cpu_intc_offset < 0)
+			continue;
+
+		cpu_offset = fdt_parent_offset(fdt, cpu_intc_offset);
+		if (cpu_offset < 0)
+			continue;
+
+		rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
+
+		if (rc)
+			continue;
+
+		if (SBI_HARTMASK_MAX_BITS <= hartid)
+			continue;
+
+		if (hwirq == IRQ_M_SOFT)
+			hcount++;
+	}
+
+	*hart_count = hcount;
+
+	return 0;
+}
+
 int fdt_parse_compat_addr(void *fdt, uint64_t *addr,
 			  const char *compatible)
 {
diff --git a/lib/utils/fdt/fdt_pmu.c b/lib/utils/fdt/fdt_pmu.c
index 8ba6b08..4829f21 100644
--- a/lib/utils/fdt/fdt_pmu.c
+++ b/lib/utils/fdt/fdt_pmu.c
@@ -75,42 +75,52 @@ int fdt_pmu_setup(void *fdt)
 	if (pmu_offset < 0)
 		return SBI_EFAIL;
 
-	event_ctr_map = fdt_getprop(fdt, pmu_offset, "riscv,event-to-mhpmcounters", &len);
-	if (!event_ctr_map || len < 8)
-		return SBI_EFAIL;
-	len = len / (sizeof(u32) * 3);
-	for (i = 0; i < len; i++) {
-		event_idx_start = fdt32_to_cpu(event_ctr_map[3 * i]);
-		event_idx_end = fdt32_to_cpu(event_ctr_map[3 * i + 1]);
-		ctr_map = fdt32_to_cpu(event_ctr_map[3 * i + 2]);
-		sbi_pmu_add_hw_event_counter_map(event_idx_start, event_idx_end, ctr_map);
+	event_ctr_map = fdt_getprop(fdt, pmu_offset,
+				    "riscv,event-to-mhpmcounters", &len);
+	if (event_ctr_map && len >= 8) {
+		len = len / (sizeof(u32) * 3);
+		for (i = 0; i < len; i++) {
+			event_idx_start = fdt32_to_cpu(event_ctr_map[3 * i]);
+			event_idx_end = fdt32_to_cpu(event_ctr_map[3 * i + 1]);
+			ctr_map = fdt32_to_cpu(event_ctr_map[3 * i + 2]);
+			result = sbi_pmu_add_hw_event_counter_map(
+				event_idx_start, event_idx_end, ctr_map);
+			if (result)
+				return result;
+		}
 	}
 
-	event_val = fdt_getprop(fdt, pmu_offset, "riscv,event-to-mhpmevent", &len);
-	if (!event_val || len < 8)
-		return SBI_EFAIL;
-	len = len / (sizeof(u32) * 3);
-	for (i = 0; i < len; i++) {
-		event = &fdt_pmu_evt_select[hw_event_count];
-		event->eidx = fdt32_to_cpu(event_val[3 * i]);
-		event->select = fdt32_to_cpu(event_val[3 * i + 1]);
-		event->select = (event->select << 32) | fdt32_to_cpu(event_val[3 * i + 2]);
-		hw_event_count++;
+	event_val = fdt_getprop(fdt, pmu_offset,
+				"riscv,event-to-mhpmevent", &len);
+	if (event_val && len >= 8) {
+		len = len / (sizeof(u32) * 3);
+		for (i = 0; i < len; i++) {
+			event = &fdt_pmu_evt_select[hw_event_count];
+			event->eidx = fdt32_to_cpu(event_val[3 * i]);
+			event->select = fdt32_to_cpu(event_val[3 * i + 1]);
+			event->select = (event->select << 32) |
+					fdt32_to_cpu(event_val[3 * i + 2]);
+			hw_event_count++;
+		}
 	}
 
-	event_val = fdt_getprop(fdt, pmu_offset, "riscv,raw-event-to-mhpmcounters", &len);
-	if (!event_val || len < 20)
-		return SBI_EFAIL;
-	len = len / (sizeof(u32) * 5);
-	for (i = 0; i < len; i++) {
-		raw_selector = fdt32_to_cpu(event_val[5 * i]);
-		raw_selector = (raw_selector << 32) | fdt32_to_cpu(event_val[5 * i + 1]);
-		select_mask = fdt32_to_cpu(event_val[5 * i + 2]);
-		select_mask = (select_mask  << 32) | fdt32_to_cpu(event_val[5 * i + 3]);
-		ctr_map = fdt32_to_cpu(event_val[5 * i + 4]);
-		result = sbi_pmu_add_raw_event_counter_map(raw_selector, select_mask, ctr_map);
-		if (!result)
-			hw_event_count++;
+	event_val = fdt_getprop(fdt, pmu_offset,
+				"riscv,raw-event-to-mhpmcounters", &len);
+	if (event_val && len >= 20) {
+		len = len / (sizeof(u32) * 5);
+		for (i = 0; i < len; i++) {
+			raw_selector = fdt32_to_cpu(event_val[5 * i]);
+			raw_selector = (raw_selector << 32) |
+					fdt32_to_cpu(event_val[5 * i + 1]);
+			select_mask = fdt32_to_cpu(event_val[5 * i + 2]);
+			select_mask = (select_mask  << 32) |
+					fdt32_to_cpu(event_val[5 * i + 3]);
+			ctr_map = fdt32_to_cpu(event_val[5 * i + 4]);
+			result = sbi_pmu_add_raw_event_counter_map(
+					raw_selector, select_mask, ctr_map);
+			if (result)
+				return result;
+		}
 	}
 
 	return 0;
diff --git a/lib/utils/fdt/objects.mk b/lib/utils/fdt/objects.mk
index 03800f9..5cede81 100644
--- a/lib/utils/fdt/objects.mk
+++ b/lib/utils/fdt/objects.mk
@@ -4,7 +4,7 @@
 # Copyright (C) 2020 Bin Meng <bmeng.cn@gmail.com>
 #
 
-libsbiutils-objs-y += fdt/fdt_domain.o
-libsbiutils-objs-y += fdt/fdt_pmu.o
-libsbiutils-objs-y += fdt/fdt_helper.o
-libsbiutils-objs-y += fdt/fdt_fixup.o
+libsbiutils-objs-$(CONFIG_FDT_DOMAIN) += fdt/fdt_domain.o
+libsbiutils-objs-$(CONFIG_FDT_PMU) += fdt/fdt_pmu.o
+libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_helper.o
+libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_fixup.o
diff --git a/lib/utils/gpio/Kconfig b/lib/utils/gpio/Kconfig
new file mode 100644
index 0000000..38a9d75
--- /dev/null
+++ b/lib/utils/gpio/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "GPIO Support"
+
+config FDT_GPIO
+	bool "FDT based GPIO drivers"
+	depends on FDT
+	select GPIO
+	default n
+
+if FDT_GPIO
+
+config FDT_GPIO_SIFIVE
+	bool "SiFive GPIO FDT driver"
+	default n
+
+endif
+
+config GPIO
+	bool "GPIO support"
+	default n
+
+endmenu
diff --git a/lib/utils/gpio/objects.mk b/lib/utils/gpio/objects.mk
index a5e131b..eedd699 100644
--- a/lib/utils/gpio/objects.mk
+++ b/lib/utils/gpio/objects.mk
@@ -7,10 +7,10 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += gpio/fdt_gpio.o
-libsbiutils-objs-y += gpio/fdt_gpio_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_GPIO) += gpio/fdt_gpio.o
+libsbiutils-objs-$(CONFIG_FDT_GPIO) += gpio/fdt_gpio_drivers.o
 
-carray-fdt_gpio_drivers-y += fdt_gpio_sifive
-libsbiutils-objs-y += gpio/fdt_gpio_sifive.o
+carray-fdt_gpio_drivers-$(CONFIG_FDT_GPIO_SIFIVE) += fdt_gpio_sifive
+libsbiutils-objs-$(CONFIG_FDT_GPIO_SIFIVE) += gpio/fdt_gpio_sifive.o
 
-libsbiutils-objs-y += gpio/gpio.o
+libsbiutils-objs-$(CONFIG_GPIO) += gpio/gpio.o
diff --git a/lib/utils/i2c/Kconfig b/lib/utils/i2c/Kconfig
new file mode 100644
index 0000000..46a3454
--- /dev/null
+++ b/lib/utils/i2c/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "I2C Support"
+
+config FDT_I2C
+	bool "FDT based I2C drivers"
+	depends on FDT
+	select I2C
+	default n
+
+if FDT_I2C
+
+config FDT_I2C_SIFIVE
+	bool "SiFive I2C FDT driver"
+	default n
+
+endif
+
+config I2C
+	bool "I2C support"
+	default n
+
+endmenu
diff --git a/lib/utils/i2c/objects.mk b/lib/utils/i2c/objects.mk
index bb0d6d5..a0fbbb5 100644
--- a/lib/utils/i2c/objects.mk
+++ b/lib/utils/i2c/objects.mk
@@ -7,10 +7,10 @@
 #   Nikita Shubin <n.shubin@yadro.com>
 #
 
-libsbiutils-objs-y += i2c/i2c.o
+libsbiutils-objs-$(CONFIG_I2C) += i2c/i2c.o
 
-libsbiutils-objs-y += i2c/fdt_i2c.o
-libsbiutils-objs-y += i2c/fdt_i2c_adapter_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_I2C) += i2c/fdt_i2c.o
+libsbiutils-objs-$(CONFIG_FDT_I2C) += i2c/fdt_i2c_adapter_drivers.o
 
-carray-fdt_i2c_adapter_drivers-y += fdt_i2c_adapter_sifive
-libsbiutils-objs-y += i2c/fdt_i2c_sifive.o
+carray-fdt_i2c_adapter_drivers-$(CONFIG_FDT_I2C_SIFIVE) += fdt_i2c_adapter_sifive
+libsbiutils-objs-$(CONFIG_FDT_I2C_SIFIVE) += i2c/fdt_i2c_sifive.o
diff --git a/lib/utils/ipi/Kconfig b/lib/utils/ipi/Kconfig
new file mode 100644
index 0000000..3718852
--- /dev/null
+++ b/lib/utils/ipi/Kconfig
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "IPI Device Support"
+
+config FDT_IPI
+	bool "FDT based ipi drivers"
+	depends on FDT
+	default n
+
+if FDT_IPI
+
+config FDT_IPI_MSWI
+	bool "ACLINT MSWI FDT driver"
+	select IPI_MSWI
+	default n
+
+config FDT_IPI_PLICSW
+	bool "Andes PLICSW FDT driver"
+	select IPI_PLICSW
+	default n
+
+endif
+
+config IPI_MSWI
+	bool "ACLINT MSWI support"
+	default n
+
+config IPI_PLICSW
+	bool "Andes PLICSW support"
+	default n
+
+endmenu
diff --git a/lib/utils/ipi/andes_plicsw.c b/lib/utils/ipi/andes_plicsw.c
new file mode 100644
index 0000000..db25ae2
--- /dev/null
+++ b/lib/utils/ipi/andes_plicsw.c
@@ -0,0 +1,137 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Zong Li <zong@andestech.com>
+ *   Nylon Chen <nylon7@andestech.com>
+ *   Leo Yu-Chi Liang <ycliang@andestech.com>
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#include <sbi/riscv_asm.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_ipi.h>
+#include <sbi_utils/ipi/andes_plicsw.h>
+
+struct plicsw_data plicsw;
+
+static inline void plicsw_claim(void)
+{
+	u32 hartid = current_hartid();
+
+	if (plicsw.hart_count <= hartid)
+		ebreak();
+
+	plicsw.source_id[hartid] =
+		readl((void *)plicsw.addr + PLICSW_CONTEXT_BASE +
+		      PLICSW_CONTEXT_CLAIM + PLICSW_CONTEXT_STRIDE * hartid);
+}
+
+static inline void plicsw_complete(void)
+{
+	u32 hartid = current_hartid();
+	u32 source = plicsw.source_id[hartid];
+
+	writel(source, (void *)plicsw.addr + PLICSW_CONTEXT_BASE +
+			       PLICSW_CONTEXT_CLAIM +
+			       PLICSW_CONTEXT_STRIDE * hartid);
+}
+
+static inline void plic_sw_pending(u32 target_hart)
+{
+	/*
+	 * The pending array registers are w1s type.
+	 * IPI pending array mapping as following:
+	 *
+	 * Pending array start address: base + 0x1000
+	 * ---------------------------------
+	 * | hart3 | hart2 | hart1 | hart0 |
+	 * ---------------------------------
+	 * Each hartX can send IPI to another hart by setting the
+	 * bitY to its own region (see the below).
+	 *
+	 * In each hartX region:
+	 * <---------- PICSW_PENDING_STRIDE -------->
+	 * | bit7 | ... | bit3 | bit2 | bit1 | bit0 |
+	 * ------------------------------------------
+	 * The bitY of hartX region indicates that hartX sends an
+	 * IPI to hartY.
+	 */
+	u32 hartid	    = current_hartid();
+	u32 word_index	    = hartid / 4;
+	u32 per_hart_offset = PLICSW_PENDING_STRIDE * hartid;
+	u32 val		    = 1 << target_hart << per_hart_offset;
+
+	writel(val, (void *)plicsw.addr + PLICSW_PENDING_BASE + word_index * 4);
+}
+
+static void plicsw_ipi_send(u32 target_hart)
+{
+	if (plicsw.hart_count <= target_hart)
+		ebreak();
+
+	/* Set PLICSW IPI */
+	plic_sw_pending(target_hart);
+}
+
+static void plicsw_ipi_clear(u32 target_hart)
+{
+	if (plicsw.hart_count <= target_hart)
+		ebreak();
+
+	/* Clear PLICSW IPI */
+	plicsw_claim();
+	plicsw_complete();
+}
+
+static struct sbi_ipi_device plicsw_ipi = {
+	.name      = "andes_plicsw",
+	.ipi_send  = plicsw_ipi_send,
+	.ipi_clear = plicsw_ipi_clear
+};
+
+int plicsw_warm_ipi_init(void)
+{
+	u32 hartid = current_hartid();
+
+	/* Clear PLICSW IPI */
+	plicsw_ipi_clear(hartid);
+
+	return 0;
+}
+
+int plicsw_cold_ipi_init(struct plicsw_data *plicsw)
+{
+	int rc;
+
+	/* Setup source priority */
+	uint32_t *priority = (void *)plicsw->addr + PLICSW_PRIORITY_BASE;
+
+	for (int i = 0; i < plicsw->hart_count; i++)
+		writel(1, &priority[i]);
+
+	/* Setup target enable */
+	uint32_t enable_mask = PLICSW_HART_MASK;
+
+	for (int i = 0; i < plicsw->hart_count; i++) {
+		uint32_t *enable = (void *)plicsw->addr + PLICSW_ENABLE_BASE +
+				   PLICSW_ENABLE_STRIDE * i;
+		writel(enable_mask, enable);
+		writel(enable_mask, enable + 1);
+		enable_mask <<= 1;
+	}
+
+	/* Add PLICSW region to the root domain */
+	rc = sbi_domain_root_add_memrange(plicsw->addr, plicsw->size,
+					  PLICSW_REGION_ALIGN,
+					  SBI_DOMAIN_MEMREGION_MMIO);
+	if (rc)
+		return rc;
+
+	sbi_ipi_set_device(&plicsw_ipi);
+
+	return 0;
+}
diff --git a/lib/utils/ipi/fdt_ipi_mswi.c b/lib/utils/ipi/fdt_ipi_mswi.c
index 0176941..4dc91f2 100644
--- a/lib/utils/ipi/fdt_ipi_mswi.c
+++ b/lib/utils/ipi/fdt_ipi_mswi.c
@@ -56,6 +56,7 @@ static const unsigned long clint_offset = CLINT_MSWI_OFFSET;
 static const struct fdt_match ipi_mswi_match[] = {
 	{ .compatible = "riscv,clint0", .data = &clint_offset },
 	{ .compatible = "sifive,clint0", .data = &clint_offset },
+	{ .compatible = "thead,c900-clint", .data = &clint_offset },
 	{ .compatible = "riscv,aclint-mswi" },
 	{ },
 };
diff --git a/lib/utils/ipi/fdt_ipi_plicsw.c b/lib/utils/ipi/fdt_ipi_plicsw.c
new file mode 100644
index 0000000..bf29bfc
--- /dev/null
+++ b/lib/utils/ipi/fdt_ipi_plicsw.c
@@ -0,0 +1,47 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Zong Li <zong@andestech.com>
+ *   Nylon Chen <nylon7@andestech.com>
+ *   Leo Yu-Chi Liang <ycliang@andestech.com>
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/ipi/fdt_ipi.h>
+#include <sbi_utils/ipi/andes_plicsw.h>
+
+extern struct plicsw_data plicsw;
+
+int fdt_plicsw_cold_ipi_init(void *fdt, int nodeoff,
+				const struct fdt_match *match)
+{
+	int rc;
+
+	rc = fdt_parse_plicsw_node(fdt, nodeoff, &plicsw.addr, &plicsw.size,
+				   &plicsw.hart_count);
+	if (rc)
+		return rc;
+
+	rc = plicsw_cold_ipi_init(&plicsw);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static const struct fdt_match ipi_plicsw_match[] = {
+	{ .compatible = "andestech,plicsw" },
+	{},
+};
+
+struct fdt_ipi fdt_ipi_plicsw = {
+	.match_table = ipi_plicsw_match,
+	.cold_init   = fdt_plicsw_cold_ipi_init,
+	.warm_init   = plicsw_warm_ipi_init,
+	.exit	     = NULL,
+};
diff --git a/lib/utils/ipi/objects.mk b/lib/utils/ipi/objects.mk
index 0b0bc2d..999e7bd 100644
--- a/lib/utils/ipi/objects.mk
+++ b/lib/utils/ipi/objects.mk
@@ -7,10 +7,14 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += ipi/aclint_mswi.o
+libsbiutils-objs-$(CONFIG_IPI_MSWI) += ipi/aclint_mswi.o
+libsbiutils-objs-$(CONFIG_IPI_PLICSW) += ipi/andes_plicsw.o
 
-libsbiutils-objs-y += ipi/fdt_ipi.o
-libsbiutils-objs-y += ipi/fdt_ipi_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_IPI) += ipi/fdt_ipi.o
+libsbiutils-objs-$(CONFIG_FDT_IPI) += ipi/fdt_ipi_drivers.o
 
-carray-fdt_ipi_drivers-y += fdt_ipi_mswi
-libsbiutils-objs-y += ipi/fdt_ipi_mswi.o
+carray-fdt_ipi_drivers-$(CONFIG_FDT_IPI_MSWI) += fdt_ipi_mswi
+libsbiutils-objs-$(CONFIG_FDT_IPI_MSWI) += ipi/fdt_ipi_mswi.o
+
+carray-fdt_ipi_drivers-$(CONFIG_FDT_IPI_PLICSW) += fdt_ipi_plicsw
+libsbiutils-objs-$(CONFIG_FDT_IPI_PLICSW) += ipi/fdt_ipi_plicsw.o
diff --git a/lib/utils/irqchip/Kconfig b/lib/utils/irqchip/Kconfig
new file mode 100644
index 0000000..7a71388
--- /dev/null
+++ b/lib/utils/irqchip/Kconfig
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "Interrupt Controller Support"
+
+config FDT_IRQCHIP
+	bool "FDT based interrupt controller drivers"
+	depends on FDT
+	default n
+
+if FDT_IRQCHIP
+
+config FDT_IRQCHIP_APLIC
+	bool "Advanced Platform Level Interrupt Controller (APLIC) FDT driver"
+	select IRQCHIP_APLIC
+	default n
+
+config FDT_IRQCHIP_IMSIC
+	bool "Incoming Message Signalled Interrupt Controller (IMSIC) FDT driver"
+	select IRQCHIP_IMSIC
+	default n
+
+config FDT_IRQCHIP_PLIC
+	bool "Platform Level Interrupt Controller (PLIC) FDT driver"
+	select IRQCHIP_PLIC
+	default n
+
+endif
+
+config IRQCHIP_APLIC
+	bool "Advanced Platform Level Interrupt Controller (APLIC) support"
+	default n
+
+config IRQCHIP_IMSIC
+	bool "Incoming Message Signalled Interrupt Controller (IMSIC) support"
+	default n
+
+config IRQCHIP_PLIC
+	bool "Platform Level Interrupt Controller (PLIC) support"
+	default n
+
+endmenu
diff --git a/lib/utils/irqchip/fdt_irqchip_plic.c b/lib/utils/irqchip/fdt_irqchip_plic.c
index a6e185c..1aadf91 100644
--- a/lib/utils/irqchip/fdt_irqchip_plic.c
+++ b/lib/utils/irqchip/fdt_irqchip_plic.c
@@ -24,36 +24,37 @@ static struct plic_data plic[PLIC_MAX_NR];
 static struct plic_data *plic_hartid2data[SBI_HARTMASK_MAX_BITS];
 static int plic_hartid2context[SBI_HARTMASK_MAX_BITS][2];
 
-void fdt_plic_priority_save(u8 *priority)
+void fdt_plic_priority_save(u8 *priority, u32 num)
 {
 	struct plic_data *plic = plic_hartid2data[current_hartid()];
 
-	plic_priority_save(plic, priority);
+	plic_priority_save(plic, priority, num);
 }
 
-void fdt_plic_priority_restore(const u8 *priority)
+void fdt_plic_priority_restore(const u8 *priority, u32 num)
 {
 	struct plic_data *plic = plic_hartid2data[current_hartid()];
 
-	plic_priority_restore(plic, priority);
+	plic_priority_restore(plic, priority, num);
 }
 
-void fdt_plic_context_save(bool smode, u32 *enable, u32 *threshold)
+void fdt_plic_context_save(bool smode, u32 *enable, u32 *threshold, u32 num)
 {
 	u32 hartid = current_hartid();
 
 	plic_context_save(plic_hartid2data[hartid],
 			  plic_hartid2context[hartid][smode],
-			  enable, threshold);
+			  enable, threshold, num);
 }
 
-void fdt_plic_context_restore(bool smode, const u32 *enable, u32 threshold)
+void fdt_plic_context_restore(bool smode, const u32 *enable, u32 threshold,
+			      u32 num)
 {
 	u32 hartid = current_hartid();
 
 	plic_context_restore(plic_hartid2data[hartid],
 			     plic_hartid2context[hartid][smode],
-			     enable, threshold);
+			     enable, threshold, num);
 }
 
 static int irqchip_plic_warm_init(void)
@@ -86,7 +87,7 @@ static int irqchip_plic_update_hartid_table(void *fdt, int nodeoff,
 			continue;
 
 		cpu_offset = fdt_parent_offset(fdt, cpu_intc_offset);
-		if (cpu_intc_offset < 0)
+		if (cpu_offset < 0)
 			continue;
 
 		err = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
@@ -159,11 +160,12 @@ void thead_plic_restore(void)
 }
 
 static const struct fdt_match irqchip_plic_match[] = {
+	{ .compatible = "andestech,nceplic100" },
 	{ .compatible = "riscv,plic0" },
 	{ .compatible = "sifive,plic-1.0.0" },
 	{ .compatible = "thead,c900-plic",
 	  .data = thead_plic_plat_init },
-	{ },
+	{ /* sentinel */ }
 };
 
 struct fdt_irqchip fdt_irqchip_plic = {
diff --git a/lib/utils/irqchip/objects.mk b/lib/utils/irqchip/objects.mk
index 7775bc4..e458891 100644
--- a/lib/utils/irqchip/objects.mk
+++ b/lib/utils/irqchip/objects.mk
@@ -7,18 +7,18 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += irqchip/fdt_irqchip.o
-libsbiutils-objs-y += irqchip/fdt_irqchip_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_IRQCHIP) += irqchip/fdt_irqchip.o
+libsbiutils-objs-$(CONFIG_FDT_IRQCHIP) += irqchip/fdt_irqchip_drivers.o
 
-carray-fdt_irqchip_drivers-y += fdt_irqchip_aplic
-libsbiutils-objs-y += irqchip/fdt_irqchip_aplic.o
+carray-fdt_irqchip_drivers-$(CONFIG_FDT_IRQCHIP_APLIC) += fdt_irqchip_aplic
+libsbiutils-objs-$(CONFIG_FDT_IRQCHIP_APLIC) += irqchip/fdt_irqchip_aplic.o
 
-carray-fdt_irqchip_drivers-y += fdt_irqchip_imsic
-libsbiutils-objs-y += irqchip/fdt_irqchip_imsic.o
+carray-fdt_irqchip_drivers-$(CONFIG_FDT_IRQCHIP_IMSIC) += fdt_irqchip_imsic
+libsbiutils-objs-$(CONFIG_FDT_IRQCHIP_IMSIC) += irqchip/fdt_irqchip_imsic.o
 
-carray-fdt_irqchip_drivers-y += fdt_irqchip_plic
-libsbiutils-objs-y += irqchip/fdt_irqchip_plic.o
+carray-fdt_irqchip_drivers-$(CONFIG_FDT_IRQCHIP_PLIC) += fdt_irqchip_plic
+libsbiutils-objs-$(CONFIG_FDT_IRQCHIP_PLIC) += irqchip/fdt_irqchip_plic.o
 
-libsbiutils-objs-y += irqchip/aplic.o
-libsbiutils-objs-y += irqchip/imsic.o
-libsbiutils-objs-y += irqchip/plic.o
+libsbiutils-objs-$(CONFIG_IRQCHIP_APLIC) += irqchip/aplic.o
+libsbiutils-objs-$(CONFIG_IRQCHIP_IMSIC) += irqchip/imsic.o
+libsbiutils-objs-$(CONFIG_IRQCHIP_PLIC) += irqchip/plic.o
diff --git a/lib/utils/irqchip/plic.c b/lib/utils/irqchip/plic.c
index 73d7788..d633514 100644
--- a/lib/utils/irqchip/plic.c
+++ b/lib/utils/irqchip/plic.c
@@ -36,15 +36,16 @@ static void plic_set_priority(const struct plic_data *plic, u32 source, u32 val)
 	writel(val, plic_priority);
 }
 
-void plic_priority_save(const struct plic_data *plic, u8 *priority)
+void plic_priority_save(const struct plic_data *plic, u8 *priority, u32 num)
 {
-	for (u32 i = 0; i < plic->num_src; i++)
+	for (u32 i = 1; i <= num; i++)
 		priority[i] = plic_get_priority(plic, i);
 }
 
-void plic_priority_restore(const struct plic_data *plic, const u8 *priority)
+void plic_priority_restore(const struct plic_data *plic, const u8 *priority,
+			   u32 num)
 {
-	for (u32 i = 0; i < plic->num_src; i++)
+	for (u32 i = 1; i <= num; i++)
 		plic_set_priority(plic, i, priority[i]);
 }
 
@@ -91,22 +92,28 @@ static void plic_set_ie(const struct plic_data *plic, u32 cntxid,
 }
 
 void plic_context_save(const struct plic_data *plic, int context_id,
-		       u32 *enable, u32 *threshold)
+		       u32 *enable, u32 *threshold, u32 num)
 {
-	u32 ie_words = (plic->num_src + 31) / 32;
+	u32 ie_words = plic->num_src / 32 + 1;
 
-	for (u32 i = 0; i < ie_words; i++)
+	if (num > ie_words)
+		num = ie_words;
+
+	for (u32 i = 0; i < num; i++)
 		enable[i] = plic_get_ie(plic, context_id, i);
 
 	*threshold = plic_get_thresh(plic, context_id);
 }
 
 void plic_context_restore(const struct plic_data *plic, int context_id,
-			  const u32 *enable, u32 threshold)
+			  const u32 *enable, u32 threshold, u32 num)
 {
-	u32 ie_words = (plic->num_src + 31) / 32;
+	u32 ie_words = plic->num_src / 32 + 1;
 
-	for (u32 i = 0; i < ie_words; i++)
+	if (num > ie_words)
+		num = ie_words;
+
+	for (u32 i = 0; i < num; i++)
 		plic_set_ie(plic, context_id, i, enable[i]);
 
 	plic_set_thresh(plic, context_id, threshold);
@@ -120,7 +127,7 @@ int plic_context_init(const struct plic_data *plic, int context_id,
 	if (!plic || context_id < 0)
 		return SBI_EINVAL;
 
-	ie_words = (plic->num_src + 31) / 32;
+	ie_words = plic->num_src / 32 + 1;
 	ie_value = enable ? 0xffffffffU : 0U;
 
 	for (u32 i = 0; i < ie_words; i++)
diff --git a/lib/utils/libfdt/Kconfig b/lib/utils/libfdt/Kconfig
new file mode 100644
index 0000000..d1cecf8
--- /dev/null
+++ b/lib/utils/libfdt/Kconfig
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config LIBFDT
+	bool
+	default n
diff --git a/lib/utils/libfdt/objects.mk b/lib/utils/libfdt/objects.mk
index 8c060df..0e01b54 100644
--- a/lib/utils/libfdt/objects.mk
+++ b/lib/utils/libfdt/objects.mk
@@ -12,5 +12,5 @@ libfdt_files = fdt.o fdt_addresses.o fdt_check.o fdt_empty_tree.o fdt_ro.o fdt_r
 $(foreach file, $(libfdt_files), \
         $(eval CFLAGS_$(file) = -I$(src)/../../utils/libfdt))
 
-libsbiutils-objs-y += $(addprefix libfdt/,$(libfdt_files))
+libsbiutils-objs-$(CONFIG_LIBFDT) += $(addprefix libfdt/,$(libfdt_files))
 libsbiutils-genflags-y  += -I$(libsbiutils_dir)/libfdt/
diff --git a/lib/utils/reset/Kconfig b/lib/utils/reset/Kconfig
new file mode 100644
index 0000000..0e0c2c1
--- /dev/null
+++ b/lib/utils/reset/Kconfig
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "System Reset Support"
+
+config FDT_RESET
+	bool "FDT based reset drivers"
+	depends on FDT
+	default n
+
+if FDT_RESET
+
+config FDT_RESET_ATCWDT200
+	bool "Andes WDT FDT reset driver"
+	default n
+
+config FDT_RESET_GPIO
+	bool "GPIO FDT reset driver"
+	depends on FDT_GPIO
+	default n
+
+config FDT_RESET_HTIF
+	bool "Host transfer interface (HTIF) FDT reset driver"
+	select SYS_HTIF
+	default n
+
+config FDT_RESET_SIFIVE_TEST
+	bool "SiFive Test FDT reset driver"
+	select SYS_SIFIVE_TEST
+	default n
+
+config FDT_RESET_SUNXI_WDT
+	bool "Sunxi WDT FDT reset driver"
+	default n
+
+config FDT_RESET_THEAD
+	bool "T-HEAD FDT reset driver"
+	default n
+
+endif
+
+endmenu
diff --git a/lib/utils/reset/fdt_reset_atcwdt200.c b/lib/utils/reset/fdt_reset_atcwdt200.c
new file mode 100644
index 0000000..91acc9f
--- /dev/null
+++ b/lib/utils/reset/fdt_reset_atcwdt200.c
@@ -0,0 +1,122 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#include <libfdt.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hart.h>
+#include <sbi/sbi_platform.h>
+#include <sbi/sbi_system.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/reset/fdt_reset.h>
+
+#define ATCWDT200_WP_NUM 0x5aa5
+#define WREN_REG 0x18
+#define CTRL_REG 0x10
+#define RST_TIME_OFF 8
+#define RST_TIME_MSK (0x3 << RST_TIME_OFF)
+#define RST_CLK_128 (0 << RST_TIME_OFF)
+#define RST_CLK_256 (1 << RST_TIME_OFF)
+#define RST_CLK_512 (2 << RST_TIME_OFF)
+#define RST_CLK_1024 (3 << RST_TIME_OFF)
+#define INT_TIME_OFF 4
+#define INT_TIME_MSK (0xf << INT_TIME_OFF)
+#define INT_CLK_64 (0 << INT_TIME_OFF)
+#define INT_CLK_256 (1 << INT_TIME_OFF)
+#define INT_CLK_1024 (2 << INT_TIME_OFF)
+#define INT_CLK_2048 (3 << INT_TIME_OFF)
+#define INT_CLK_4096 (4 << INT_TIME_OFF)
+#define INT_CLK_8192 (5 << INT_TIME_OFF)
+#define INT_CLK_16384 (6 << INT_TIME_OFF)
+#define INT_CLK_32768 (7 << INT_TIME_OFF)
+#define RST_EN (1 << 3)
+#define INT_EN (1 << 2)
+#define CLK_PCLK (1 << 1)
+#define WDT_EN (1 << 0)
+
+#define FLASH_BASE 0x80000000ULL
+#define SMU_RESET_VEC_LO_OFF 0x50
+#define SMU_RESET_VEC_HI_OFF 0x60
+#define SMU_HARTn_RESET_VEC_LO(n) (SMU_RESET_VEC_LO_OFF + (n * 0x4))
+#define SMU_HARTn_RESET_VEC_HI(n) (SMU_RESET_VEC_HI_OFF + (n * 0x4))
+
+static volatile char *wdt_addr;
+static volatile char *smu_addr;
+
+static int ae350_system_reset_check(u32 type, u32 reason)
+{
+	switch (type) {
+	case SBI_SRST_RESET_TYPE_COLD_REBOOT:
+		return 1;
+	case SBI_SRST_RESET_TYPE_SHUTDOWN:
+	case SBI_SRST_RESET_TYPE_WARM_REBOOT:
+	default:
+		return 0;
+	}
+}
+
+static void ae350_system_reset(u32 type, u32 reason)
+{
+	const struct sbi_platform *plat = sbi_platform_thishart_ptr();
+
+	for (int i = 0; i < sbi_platform_hart_count(plat); i++) {
+		writel(FLASH_BASE, smu_addr + SMU_HARTn_RESET_VEC_LO(i));
+		writel(FLASH_BASE >> 32, smu_addr + SMU_HARTn_RESET_VEC_HI(i));
+	}
+
+	/* Program WDT control register  */
+	writew(ATCWDT200_WP_NUM, wdt_addr + WREN_REG);
+	writel(INT_CLK_32768 | INT_EN | RST_CLK_128 | RST_EN | WDT_EN,
+	       wdt_addr + CTRL_REG);
+
+	sbi_hart_hang();
+}
+
+static struct sbi_system_reset_device atcwdt200_reset = {
+	.name		    = "atcwdt200",
+	.system_reset_check = ae350_system_reset_check,
+	.system_reset	    = ae350_system_reset,
+};
+
+static int atcwdt200_reset_init(void *fdt, int nodeoff,
+				const struct fdt_match *match)
+{
+	uint64_t reg_addr;
+	int rc;
+
+	rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &reg_addr, NULL);
+	if (rc < 0 || !reg_addr)
+		return SBI_ENODEV;
+
+	wdt_addr = (volatile char *)(unsigned long)reg_addr;
+
+	/*
+	 * The reset device requires smu to program the reset
+	 * vector for each hart.
+	 */
+	if (fdt_parse_compat_addr(fdt, &reg_addr, "andestech,atcsmu"))
+		return SBI_ENODEV;
+
+	smu_addr = (volatile char *)(unsigned long)reg_addr;
+
+	sbi_system_reset_add_device(&atcwdt200_reset);
+
+	return 0;
+}
+
+static const struct fdt_match atcwdt200_reset_match[] = {
+	{ .compatible = "andestech,atcwdt200" },
+	{},
+};
+
+struct fdt_reset fdt_reset_atcwdt200 = {
+	.match_table = atcwdt200_reset_match,
+	.init	     = atcwdt200_reset_init,
+};
diff --git a/lib/utils/reset/objects.mk b/lib/utils/reset/objects.mk
index 8cddcdf..c9f851c 100644
--- a/lib/utils/reset/objects.mk
+++ b/lib/utils/reset/objects.mk
@@ -7,22 +7,25 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += reset/fdt_reset.o
-libsbiutils-objs-y += reset/fdt_reset_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_RESET) += reset/fdt_reset.o
+libsbiutils-objs-$(CONFIG_FDT_RESET) += reset/fdt_reset_drivers.o
 
-carray-fdt_reset_drivers-y += fdt_poweroff_gpio
-carray-fdt_reset_drivers-y += fdt_reset_gpio
-libsbiutils-objs-y += reset/fdt_reset_gpio.o
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_ATCWDT200) += fdt_reset_atcwdt200
+libsbiutils-objs-$(CONFIG_FDT_RESET_ATCWDT200) += reset/fdt_reset_atcwdt200.o
 
-carray-fdt_reset_drivers-y += fdt_reset_htif
-libsbiutils-objs-y += reset/fdt_reset_htif.o
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_GPIO) += fdt_poweroff_gpio
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_GPIO) += fdt_reset_gpio
+libsbiutils-objs-$(CONFIG_FDT_RESET_GPIO) += reset/fdt_reset_gpio.o
 
-carray-fdt_reset_drivers-y += fdt_reset_sifive_test
-libsbiutils-objs-y += reset/fdt_reset_sifive_test.o
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_HTIF) += fdt_reset_htif
+libsbiutils-objs-$(CONFIG_FDT_RESET_HTIF) += reset/fdt_reset_htif.o
 
-carray-fdt_reset_drivers-y += fdt_reset_sunxi_wdt
-libsbiutils-objs-y += reset/fdt_reset_sunxi_wdt.o
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_SIFIVE_TEST) += fdt_reset_sifive_test
+libsbiutils-objs-$(CONFIG_FDT_RESET_SIFIVE_TEST) += reset/fdt_reset_sifive_test.o
 
-carray-fdt_reset_drivers-y += fdt_reset_thead
-libsbiutils-objs-y += reset/fdt_reset_thead.o
-libsbiutils-objs-y += reset/fdt_reset_thead_asm.o
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_SUNXI_WDT) += fdt_reset_sunxi_wdt
+libsbiutils-objs-$(CONFIG_FDT_RESET_SUNXI_WDT) += reset/fdt_reset_sunxi_wdt.o
+
+carray-fdt_reset_drivers-$(CONFIG_FDT_RESET_THEAD) += fdt_reset_thead
+libsbiutils-objs-$(CONFIG_FDT_RESET_THEAD) += reset/fdt_reset_thead.o
+libsbiutils-objs-$(CONFIG_FDT_RESET_THEAD) += reset/fdt_reset_thead_asm.o
diff --git a/lib/utils/serial/Kconfig b/lib/utils/serial/Kconfig
new file mode 100644
index 0000000..e3589ca
--- /dev/null
+++ b/lib/utils/serial/Kconfig
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "Serial Device Support"
+
+config FDT_SERIAL
+	bool "FDT based serial drivers"
+	depends on FDT
+	default n
+
+if FDT_SERIAL
+
+config FDT_SERIAL_CADENCE
+	bool "Cadence UART FDT driver"
+	select SERIAL_CADENCE
+	default n
+
+config FDT_SERIAL_GAISLER
+	bool "Gaisler UART FDT driver"
+	select SERIAL_GAISLER
+	default n
+
+config FDT_SERIAL_HTIF
+	bool "Host transfer interface (HTIF) UART FDT driver"
+	select SYS_HTIF
+	default n
+
+config FDT_SERIAL_RENESAS_SCIF
+	bool "Renesas SCIF FDT driver"
+	select SERIAL_RENESAS_SCIF
+	default n
+
+config FDT_SERIAL_SHAKTI
+	bool "Shakti UART FDT driver"
+	select SERIAL_SHAKTI
+	default n
+
+config FDT_SERIAL_SIFIVE
+	bool "SiFive UART FDT driver"
+	select SERIAL_SIFIVE
+	default n
+
+config FDT_SERIAL_LITEX
+	bool "LiteX UART FDT driver"
+	select SERIAL_LITEX
+	default n
+
+config FDT_SERIAL_UART8250
+	bool "8250 UART FDT driver"
+	select SERIAL_UART8250
+	default n
+
+config FDT_SERIAL_XILINX_UARTLITE
+	bool "Xilinx UART Lite FDT driver"
+	select SERIAL_XILINX_UARTLITE
+	default n
+
+endif
+
+config SERIAL_CADENCE
+	bool "Cadence UART support"
+	default n
+
+config SERIAL_GAISLER
+	bool "Gaisler UART support"
+	default n
+
+config SERIAL_RENESAS_SCIF
+	bool "Renesas SCIF support"
+	default n
+
+config SERIAL_SHAKTI
+	bool "Shakti UART support"
+	default n
+
+config SERIAL_SIFIVE
+	bool "SiFive UART support"
+	default n
+
+config SERIAL_LITEX
+	bool "LiteX UART support"
+	default n
+
+config SERIAL_UART8250
+	bool "8250 UART support"
+	default n
+
+config SERIAL_XILINX_UARTLITE
+	bool "Xilinx UART Lite support"
+	default n
+
+config SERIAL_SEMIHOSTING
+	bool "Semihosting support"
+	default n
+
+endmenu
diff --git a/lib/utils/serial/cadence-uart.c b/lib/utils/serial/cadence-uart.c
new file mode 100644
index 0000000..8a2b6ba
--- /dev/null
+++ b/lib/utils/serial/cadence-uart.c
@@ -0,0 +1,128 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 StarFive Technology Co., Ltd.
+ *
+ * Author: Jun Liang Tan <junliang.tan@linux.starfivetech.com>
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi_utils/serial/cadence-uart.h>
+
+/* clang-format off */
+
+#define UART_REG_CTRL		0x00
+#define UART_REG_MODE		0x04
+#define UART_REG_IDR		0x0C
+#define UART_REG_BRGR		0x18
+#define UART_REG_CSR		0x2C
+#define UART_REG_RFIFO_TFIFO	0x30
+#define UART_REG_BDIVR		0x34
+
+#define UART_CTRL_RXRES		0x00000001
+#define UART_CTRL_TXRES		0x00000002
+#define UART_CTRL_RXEN		0x00000004
+#define UART_CTRL_RXDIS		0x00000008
+#define UART_CTRL_TXEN		0x00000010
+#define UART_CTRL_TXDIS		0x00000020
+
+#define UART_MODE_PAR_NONE	0x00000020	/* No parity set */
+
+#define UART_BRGR_CD_CLKDIVISOR	0x00000001	/* baud_sample = sel_clk */
+
+#define	UART_CSR_REMPTY		0x00000002
+#define	UART_CSR_TFUL		0x00000010
+
+/* clang-format on */
+
+static volatile void *uart_base;
+static u32 uart_in_freq;
+static u32 uart_baudrate;
+
+/*
+ * Find minimum divisor divides in_freq to max_target_hz;
+ * Based on SiFive UART driver (sifive-uart.c)
+ */
+static inline unsigned int uart_min_clk_divisor(uint64_t in_freq,
+						uint64_t max_target_hz)
+{
+	uint64_t quotient = (in_freq + max_target_hz - 1) / (max_target_hz);
+
+	/* Avoid underflow */
+	if (quotient == 0)
+		return 0;
+	else
+		return quotient - 1;
+}
+
+static u32 get_reg(u32 offset)
+{
+	return readl(uart_base + offset);
+}
+
+static void set_reg(u32 offset, u32 val)
+{
+	writel(val, uart_base + offset);
+}
+
+static void cadence_uart_putc(char ch)
+{
+	while (get_reg(UART_REG_CSR) & UART_CSR_TFUL)
+		;
+
+	set_reg(UART_REG_RFIFO_TFIFO, ch);
+}
+
+static int cadence_uart_getc(void)
+{
+	u32 ret = get_reg(UART_REG_CSR);
+
+	if (!(ret & UART_CSR_REMPTY))
+		return get_reg(UART_REG_RFIFO_TFIFO);
+
+	return -1;
+}
+
+static struct sbi_console_device cadence_console = {
+	.name = "cadence_uart",
+	.console_putc = cadence_uart_putc,
+	.console_getc = cadence_uart_getc
+};
+
+int cadence_uart_init(unsigned long base, u32 in_freq, u32 baudrate)
+{
+	uart_base     = (volatile void *)base;
+	uart_in_freq  = in_freq;
+	uart_baudrate = baudrate;
+
+	/* Disable interrupts */
+	set_reg(UART_REG_IDR, 0xFFFFFFFF);
+
+	/* Disable TX RX */
+	set_reg(UART_REG_CTRL, UART_CTRL_TXDIS | UART_CTRL_RXDIS);
+
+	/* Configure baudrate */
+	if (in_freq && baudrate) {
+		set_reg(UART_REG_BRGR, UART_BRGR_CD_CLKDIVISOR);
+		set_reg(UART_REG_BDIVR,
+			uart_min_clk_divisor(in_freq, baudrate));
+	}
+
+	/* Software reset TX RX data path and enable TX RX */
+	set_reg(UART_REG_CTRL, UART_CTRL_TXRES | UART_CTRL_RXRES
+		| UART_CTRL_TXEN | UART_CTRL_RXEN);
+
+	/*
+	 * Set:
+	 * 1 stop bit, bits[07:06] = 0x00,
+	 * no parity set, bits[05:03] = 0x100,
+	 * 8 bits character length, bits[02:01] = 0x00,
+	 * sel_clk = uart_clk, bit[0] = 0x0
+	 */
+	set_reg(UART_REG_MODE, UART_MODE_PAR_NONE);
+
+	sbi_console_set_device(&cadence_console);
+
+	return 0;
+}
diff --git a/lib/utils/serial/fdt_serial_cadence.c b/lib/utils/serial/fdt_serial_cadence.c
new file mode 100644
index 0000000..946e533
--- /dev/null
+++ b/lib/utils/serial/fdt_serial_cadence.c
@@ -0,0 +1,35 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 StarFive Technology Co., Ltd.
+ *
+ * Author: Jun Liang Tan <junliang.tan@linux.starfivetech.com>
+ */
+
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/serial/fdt_serial.h>
+#include <sbi_utils/serial/cadence-uart.h>
+
+static int serial_cadence_init(void *fdt, int nodeoff,
+			       const struct fdt_match *match)
+{
+	int rc;
+	struct platform_uart_data uart = { 0 };
+
+	rc = fdt_parse_uart_node(fdt, nodeoff, &uart);
+	if (rc)
+		return rc;
+
+	return cadence_uart_init(uart.addr, uart.freq, uart.baud);
+}
+
+static const struct fdt_match serial_cadence_match[] = {
+	{ .compatible = "cdns,uart-r1p12" },
+	{ .compatible = "starfive,jh8100-uart" },
+	{ },
+};
+
+struct fdt_serial fdt_serial_cadence = {
+	.match_table = serial_cadence_match,
+	.init = serial_cadence_init
+};
diff --git a/lib/utils/serial/fdt_serial_gaisler.c b/lib/utils/serial/fdt_serial_gaisler.c
index 9a9f9a8..74988e3 100644
--- a/lib/utils/serial/fdt_serial_gaisler.c
+++ b/lib/utils/serial/fdt_serial_gaisler.c
@@ -15,7 +15,7 @@ static int serial_gaisler_init(void *fdt, int nodeoff,
 			       const struct fdt_match *match)
 {
 	int rc;
-	struct platform_uart_data uart;
+	struct platform_uart_data uart = { 0 };
 
 	rc = fdt_parse_gaisler_uart_node(fdt, nodeoff, &uart);
 	if (rc)
diff --git a/lib/utils/serial/fdt_serial_renesas_scif.c b/lib/utils/serial/fdt_serial_renesas_scif.c
new file mode 100644
index 0000000..c331ca1
--- /dev/null
+++ b/lib/utils/serial/fdt_serial_renesas_scif.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/serial/fdt_serial.h>
+#include <sbi_utils/serial/renesas-scif.h>
+
+static int serial_renesas_scif_init(void *fdt, int nodeoff,
+				    const struct fdt_match *match)
+{
+	struct platform_uart_data uart = { 0 };
+	int ret;
+
+	ret = fdt_parse_renesas_scif_node(fdt, nodeoff, &uart);
+	if (ret)
+		return ret;
+
+	return renesas_scif_init(uart.addr, uart.freq, uart.baud);
+}
+
+static const struct fdt_match serial_renesas_scif_match[] = {
+	{ .compatible = "renesas,scif-r9a07g043" },
+	{ /* sentinel */ }
+};
+
+struct fdt_serial fdt_serial_renesas_scif = {
+	.match_table = serial_renesas_scif_match,
+	.init = serial_renesas_scif_init
+};
diff --git a/lib/utils/serial/fdt_serial_shakti.c b/lib/utils/serial/fdt_serial_shakti.c
index 4f91419..0e05630 100644
--- a/lib/utils/serial/fdt_serial_shakti.c
+++ b/lib/utils/serial/fdt_serial_shakti.c
@@ -13,7 +13,7 @@ static int serial_shakti_init(void *fdt, int nodeoff,
 				const struct fdt_match *match)
 {
 	int rc;
-	struct platform_uart_data uart;
+	struct platform_uart_data uart = { 0 };
 
 	rc = fdt_parse_shakti_uart_node(fdt, nodeoff, &uart);
 	if (rc)
diff --git a/lib/utils/serial/fdt_serial_sifive.c b/lib/utils/serial/fdt_serial_sifive.c
index f4c833c..3ca1091 100644
--- a/lib/utils/serial/fdt_serial_sifive.c
+++ b/lib/utils/serial/fdt_serial_sifive.c
@@ -15,7 +15,7 @@ static int serial_sifive_init(void *fdt, int nodeoff,
 				const struct fdt_match *match)
 {
 	int rc;
-	struct platform_uart_data uart;
+	struct platform_uart_data uart = { 0 };
 
 	rc = fdt_parse_sifive_uart_node(fdt, nodeoff, &uart);
 	if (rc)
diff --git a/lib/utils/serial/fdt_serial_uart8250.c b/lib/utils/serial/fdt_serial_uart8250.c
index 544b741..7b5d6a4 100644
--- a/lib/utils/serial/fdt_serial_uart8250.c
+++ b/lib/utils/serial/fdt_serial_uart8250.c
@@ -15,9 +15,9 @@ static int serial_uart8250_init(void *fdt, int nodeoff,
 				const struct fdt_match *match)
 {
 	int rc;
-	struct platform_uart_data uart;
+	struct platform_uart_data uart = { 0 };
 
-	rc = fdt_parse_uart8250_node(fdt, nodeoff, &uart);
+	rc = fdt_parse_uart_node(fdt, nodeoff, &uart);
 	if (rc)
 		return rc;
 
diff --git a/lib/utils/serial/fdt_serial_xlnx_uartlite.c b/lib/utils/serial/fdt_serial_xlnx_uartlite.c
index 466e16e..9f04aea 100644
--- a/lib/utils/serial/fdt_serial_xlnx_uartlite.c
+++ b/lib/utils/serial/fdt_serial_xlnx_uartlite.c
@@ -15,7 +15,7 @@ static int serial_xlnx_uartlite_init(void *fdt, int nodeoff,
 				const struct fdt_match *match)
 {
 	int rc;
-	struct platform_uart_data uart;
+	struct platform_uart_data uart = { 0 };
 
 	rc = fdt_parse_xlnx_uartlite_node(fdt, nodeoff, &uart);
 	if (rc)
diff --git a/lib/utils/serial/gaisler-uart.c b/lib/utils/serial/gaisler-uart.c
index 5f30ee4..eec75cc 100644
--- a/lib/utils/serial/gaisler-uart.c
+++ b/lib/utils/serial/gaisler-uart.c
@@ -70,7 +70,7 @@ int gaisler_uart_init(unsigned long base, u32 in_freq, u32 baudrate)
 	uart_base = (volatile char *)base;
 
 	/* Configure baudrate */
-	if (in_freq)
+	if (in_freq && baudrate)
 		set_reg(UART_REG_SCALER, in_freq / (baudrate * 8 + 7));
 
 	ctrl = get_reg(UART_REG_CTRL);
diff --git a/lib/utils/serial/objects.mk b/lib/utils/serial/objects.mk
index d26a74e..1e6bd2e 100644
--- a/lib/utils/serial/objects.mk
+++ b/lib/utils/serial/objects.mk
@@ -7,33 +7,42 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += serial/fdt_serial.o
-libsbiutils-objs-y += serial/fdt_serial_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_SERIAL) += serial/fdt_serial.o
+libsbiutils-objs-$(CONFIG_FDT_SERIAL) += serial/fdt_serial_drivers.o
 
-carray-fdt_serial_drivers-y += fdt_serial_gaisler
-libsbiutils-objs-y += serial/fdt_serial_gaisler.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_CADENCE) += fdt_serial_cadence
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_CADENCE) += serial/fdt_serial_cadence.o
 
-carray-fdt_serial_drivers-y += fdt_serial_htif
-libsbiutils-objs-y += serial/fdt_serial_htif.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_GAISLER) += fdt_serial_gaisler
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_GAISLER) += serial/fdt_serial_gaisler.o
 
-carray-fdt_serial_drivers-y += fdt_serial_shakti
-libsbiutils-objs-y += serial/fdt_serial_shakti.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_HTIF) += fdt_serial_htif
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_HTIF) += serial/fdt_serial_htif.o
 
-carray-fdt_serial_drivers-y += fdt_serial_sifive
-libsbiutils-objs-y += serial/fdt_serial_sifive.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_RENESAS_SCIF) += fdt_serial_renesas_scif
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_RENESAS_SCIF) += serial/fdt_serial_renesas_scif.o
 
-carray-fdt_serial_drivers-y += fdt_serial_litex
-libsbiutils-objs-y += serial/fdt_serial_litex.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_SHAKTI) += fdt_serial_shakti
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_SHAKTI) += serial/fdt_serial_shakti.o
 
-carray-fdt_serial_drivers-y += fdt_serial_uart8250
-libsbiutils-objs-y += serial/fdt_serial_uart8250.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_SIFIVE) += fdt_serial_sifive
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_SIFIVE) += serial/fdt_serial_sifive.o
 
-carray-fdt_serial_drivers-y += fdt_serial_xlnx_uartlite
-libsbiutils-objs-y += serial/fdt_serial_xlnx_uartlite.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_LITEX) += fdt_serial_litex
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_LITEX) += serial/fdt_serial_litex.o
 
-libsbiutils-objs-y += serial/gaisler-uart.o
-libsbiutils-objs-y += serial/shakti-uart.o
-libsbiutils-objs-y += serial/sifive-uart.o
-libsbiutils-objs-y += serial/litex-uart.o
-libsbiutils-objs-y += serial/uart8250.o
-libsbiutils-objs-y += serial/xlnx-uartlite.o
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_UART8250) += fdt_serial_uart8250
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_UART8250) += serial/fdt_serial_uart8250.o
+
+carray-fdt_serial_drivers-$(CONFIG_FDT_SERIAL_XILINX_UARTLITE) += fdt_serial_xlnx_uartlite
+libsbiutils-objs-$(CONFIG_FDT_SERIAL_XILINX_UARTLITE) += serial/fdt_serial_xlnx_uartlite.o
+
+libsbiutils-objs-$(CONFIG_SERIAL_CADENCE) += serial/cadence-uart.o
+libsbiutils-objs-$(CONFIG_SERIAL_GAISLER) += serial/gaisler-uart.o
+libsbiutils-objs-$(CONFIG_SERIAL_RENESAS_SCIF) += serial/renesas_scif.o
+libsbiutils-objs-$(CONFIG_SERIAL_SHAKTI) += serial/shakti-uart.o
+libsbiutils-objs-$(CONFIG_SERIAL_SIFIVE) += serial/sifive-uart.o
+libsbiutils-objs-$(CONFIG_SERIAL_LITEX) += serial/litex-uart.o
+libsbiutils-objs-$(CONFIG_SERIAL_UART8250) += serial/uart8250.o
+libsbiutils-objs-$(CONFIG_SERIAL_XILINX_UARTLITE) += serial/xlnx-uartlite.o
+libsbiutils-objs-$(CONFIG_SERIAL_SEMIHOSTING) += serial/semihosting.o
diff --git a/lib/utils/serial/renesas_scif.c b/lib/utils/serial/renesas_scif.c
new file mode 100644
index 0000000..cbe9db4
--- /dev/null
+++ b/lib/utils/serial/renesas_scif.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_timer.h>
+#include <sbi_utils/serial/renesas-scif.h>
+
+/* clang-format off */
+
+#define SCIF_REG_SMR		0x0
+#define SCIF_REG_BRR		0x2
+#define SCIF_REG_SCR		0x4
+#define SCIF_REG_FTDR		0x6
+#define SCIF_REG_FSR		0x8
+#define SCIF_REG_FCR		0xc
+#define SCIF_REG_LSR		0x12
+#define SCIF_REG_SEMR		0x14
+
+#define SCIF_FCR_RFRST		0x2 /* Reset assert receive-FIFO (bit[1]) */
+#define SCIF_FCR_TFRST		0x4 /* Reset assert transmit-FIFO(bit[2]) */
+
+#define SCIF_FCR_RST_ASSRT_RFTF	(SCIF_FCR_RFRST | SCIF_FCR_TFRST) /* Reset assert tx-FIFO & rx-FIFO */
+#define SCIF_FCR_RST_NGATE_RFTF	0x0 /* Reset negate tx-FIFO & rx-FIFO */
+
+#define SCIF_SCR_RE		0x10 /* Enable receive (bit[4]) */
+#define SCIF_SCR_TE		0x20 /* Enable transmit(bit[5]) */
+#define SCIF_SCR_RCV_TRN_EN	(SCIF_SCR_RE | SCIF_SCR_TE) /* Enable receive & transmit */
+#define SCIF_SCR_RCV_TRN_DIS	0x0 /* Disable receive & transmit */
+
+#define SCIF_FSR_ER		0x80 /* Receive error flag */
+#define SCIF_FSR_TEND		0x40 /* Transmit End Flag */
+#define SCIF_FSR_TDFE		0x20 /* Transmit FIFO Data Empty Flag */
+#define SCIF_FSR_BRK		0x10 /* Detect break flag */
+#define SCIF_FSR_DR		0x1  /* Receive data ready flag */
+
+#define SCIF_FSR_TXD_CHK	(SCIF_FSR_TEND | SCIF_FSR_TDFE)
+
+#define SCIF_SEMR_MDDRS		0x10 /* MDDR access enable */
+
+#define SCIF_REG_8BIT(reg)	((reg == SCIF_REG_BRR) || \
+				 (reg == SCIF_REG_FTDR) || \
+				 (reg == SCIF_REG_SEMR))
+
+#define SCBRR_VALUE(clk, baudrate) ((clk) / (32 * (baudrate)) - 1)
+
+/* clang-format on */
+
+static volatile char *scif_base;
+
+static u32 get_reg(u32 offset)
+{
+	if (SCIF_REG_8BIT(offset))
+		return readb(scif_base + offset);
+
+	return readw(scif_base + offset);
+}
+
+static void set_reg(u32 offset, u32 val)
+{
+	if (SCIF_REG_8BIT(offset))
+		writeb(val, scif_base + offset);
+	else
+		writew(val, scif_base + offset);
+}
+
+static void renesas_scif_putc(char ch)
+{
+	uint16_t reg;
+
+	while (!(SCIF_FSR_TXD_CHK & get_reg(SCIF_REG_FSR)))
+		;
+
+	set_reg(SCIF_REG_FTDR, ch);
+	reg = get_reg(SCIF_REG_FSR);
+	reg &= ~SCIF_FSR_TXD_CHK;
+	set_reg(SCIF_REG_FSR, reg);
+}
+
+static struct sbi_console_device renesas_scif_console = {
+	.name		= "renesas_scif",
+	.console_putc	= renesas_scif_putc,
+};
+
+int renesas_scif_init(unsigned long base, u32 in_freq, u32 baudrate)
+{
+	uint16_t data16;
+
+	scif_base = (volatile char *)base;
+
+	set_reg(SCIF_REG_SCR, SCIF_SCR_RCV_TRN_DIS);
+	set_reg(SCIF_REG_FCR, SCIF_FCR_RST_ASSRT_RFTF);
+
+	data16 = get_reg(SCIF_REG_FSR); /* Dummy read */
+	set_reg(SCIF_REG_FSR, 0x0); /* Clear all error bit */
+
+	data16 = get_reg(SCIF_REG_LSR); /* Dummy read */
+	set_reg(SCIF_REG_LSR, 0x0); /* Clear ORER bit */
+
+	set_reg(SCIF_REG_SCR, 0x0);
+
+	set_reg(SCIF_REG_SMR, 0x0);
+
+	data16 = get_reg(SCIF_REG_SEMR);
+	set_reg(SCIF_REG_SEMR, data16 & (~SCIF_SEMR_MDDRS));
+	set_reg(SCIF_REG_BRR, SCBRR_VALUE(in_freq, baudrate));
+
+	set_reg(SCIF_REG_FCR, SCIF_FCR_RST_NGATE_RFTF);
+	set_reg(SCIF_REG_SCR, SCIF_SCR_RCV_TRN_EN);
+
+	sbi_console_set_device(&renesas_scif_console);
+
+	return 0;
+}
diff --git a/lib/utils/serial/semihosting.c b/lib/utils/serial/semihosting.c
new file mode 100644
index 0000000..86fa296
--- /dev/null
+++ b/lib/utils/serial/semihosting.c
@@ -0,0 +1,178 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Ventana Micro Systems Inc.
+ *
+ * Authors:
+ *   Anup Patel <apatel@ventanamicro.com>
+ *   Kautuk Consul <kconsul@ventanamicro.com>
+ */
+
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_string.h>
+#include <sbi/sbi_error.h>
+#include <sbi_utils/serial/semihosting.h>
+
+#define SYSOPEN     0x01
+#define SYSWRITEC   0x03
+#define SYSREAD     0x06
+#define SYSREADC    0x07
+#define SYSERRNO	0x13
+
+static long semihosting_trap(int sysnum, void *addr)
+{
+	register int ret asm ("a0") = sysnum;
+	register void *param0 asm ("a1") = addr;
+
+	asm volatile (
+		"	.align 4\n"
+		"	.option push\n"
+		"	.option norvc\n"
+
+		"	slli zero, zero, 0x1f\n"
+		"	ebreak\n"
+		"	srai zero, zero, 7\n"
+
+		"	.option pop\n"
+		: "+r" (ret) : "r" (param0) : "memory");
+
+	return ret;
+}
+
+static bool _semihosting_enabled = true;
+static bool try_semihosting = true;
+
+bool semihosting_enabled(void)
+{
+	register int ret asm ("a0") = SYSERRNO;
+	register void *param0 asm ("a1") = NULL;
+	unsigned long tmp = 0;
+
+	if (!try_semihosting)
+		return _semihosting_enabled;
+
+	asm volatile (
+		"	.align 4\n"
+		"	.option push\n"
+		"	.option norvc\n"
+
+		"	j _semihost_test_vector_next\n"
+		"	.align 4\n"
+		"_semihost_test_vector:\n"
+		"	csrr %[en], mepc\n"
+		"	addi %[en], %[en], 4\n"
+		"	csrw mepc, %[en]\n"
+		"	add %[en], zero, zero\n"
+		"	mret\n"
+		"_semihost_test_vector_next:\n"
+
+		"	la %[tmp], _semihost_test_vector\n"
+		"	csrrw %[tmp], mtvec, %[tmp]\n"
+		"	.align 4\n"
+		"	slli zero, zero, 0x1f\n"
+		"	ebreak\n"
+		"	srai zero, zero, 7\n"
+		"	csrw mtvec, %[tmp]\n"
+
+		"	.option pop\n"
+		: [tmp] "+r" (tmp), [en] "+r" (_semihosting_enabled),
+		  [ret] "+r" (ret)
+		: "r" (param0) : "memory");
+
+	try_semihosting = false;
+	return _semihosting_enabled;
+}
+
+static int semihosting_errno(void)
+{
+	long ret = semihosting_trap(SYSERRNO, NULL);
+
+	if (ret > 0)
+		return -ret;
+	return SBI_EIO;
+}
+
+static int semihosting_infd = SBI_ENODEV;
+
+static long semihosting_open(const char *fname, enum semihosting_open_mode mode)
+{
+	long fd;
+	struct semihosting_open_s {
+		const char *fname;
+		unsigned long mode;
+		size_t len;
+	} open;
+
+	open.fname = fname;
+	open.len = sbi_strlen(fname);
+	open.mode = mode;
+
+	/* Open the file on the host */
+	fd = semihosting_trap(SYSOPEN, &open);
+	if (fd == -1)
+		return semihosting_errno();
+	return fd;
+}
+
+/**
+ * struct semihosting_rdwr_s - Arguments for read and write
+ * @fd: A file descriptor returned from semihosting_open()
+ * @memp: Pointer to a buffer of memory of at least @len bytes
+ * @len: The number of bytes to read or write
+ */
+struct semihosting_rdwr_s {
+	long fd;
+	void *memp;
+	size_t len;
+};
+
+static long semihosting_read(long fd, void *memp, size_t len)
+{
+	long ret;
+	struct semihosting_rdwr_s read;
+
+	read.fd = fd;
+	read.memp = memp;
+	read.len = len;
+
+	ret = semihosting_trap(SYSREAD, &read);
+	if (ret < 0)
+		return semihosting_errno();
+	return len - ret;
+}
+
+/* clang-format on */
+
+static void semihosting_putc(char ch)
+{
+	semihosting_trap(SYSWRITEC, &ch);
+}
+
+static int semihosting_getc(void)
+{
+	char ch = 0;
+	int ret;
+
+	if (semihosting_infd < 0)  {
+		ret = semihosting_trap(SYSREADC, NULL);
+		ret = ret < 0 ? -1 : ret;
+	} else
+		ret = semihosting_read(semihosting_infd, &ch, 1) > 0 ? ch : -1;
+
+	return ret;
+}
+
+static struct sbi_console_device semihosting_console = {
+	.name = "semihosting",
+	.console_putc = semihosting_putc,
+	.console_getc = semihosting_getc
+};
+
+int semihosting_init(void)
+{
+	semihosting_infd = semihosting_open(":tt", MODE_READ);
+
+	sbi_console_set_device(&semihosting_console);
+
+	return 0;
+}
diff --git a/lib/utils/serial/shakti-uart.c b/lib/utils/serial/shakti-uart.c
index 5f2fe75..8769cb2 100644
--- a/lib/utils/serial/shakti-uart.c
+++ b/lib/utils/serial/shakti-uart.c
@@ -19,21 +19,21 @@
 #define REG_RX_THRES	0x20
 
 #define UART_TX_FULL  0x2
+#define UART_RX_NOT_EMPTY 0x4
 #define UART_RX_FULL  0x8
 
 static volatile char *uart_base;
 
 static void shakti_uart_putc(char ch)
 {
-	while((readw(uart_base + REG_STATUS) & UART_TX_FULL))
+	while ((readb(uart_base + REG_STATUS) & UART_TX_FULL))
 		;
 	writeb(ch, uart_base + REG_TX);
 }
 
 static int shakti_uart_getc(void)
 {
-	u16 status = readw(uart_base + REG_STATUS);
-	if (status & UART_RX_FULL)
+	if (readb(uart_base + REG_STATUS) & UART_RX_NOT_EMPTY)
 		return readb(uart_base + REG_RX);
 	return -1;
 }
@@ -47,8 +47,12 @@ static struct sbi_console_device shakti_console = {
 int shakti_uart_init(unsigned long base, u32 in_freq, u32 baudrate)
 {
 	uart_base = (volatile char *)base;
-	u16 baud = (u16)(in_freq/(16 * baudrate));
-	writew(baud, uart_base + REG_BAUD);
+	u16 baud;
+
+	if (baudrate) {
+		baud = (u16)(in_freq / (16 * baudrate));
+		writew(baud, uart_base + REG_BAUD);
+	}
 
 	sbi_console_set_device(&shakti_console);
 
diff --git a/lib/utils/serial/sifive-uart.c b/lib/utils/serial/sifive-uart.c
index 9478a77..3581d47 100644
--- a/lib/utils/serial/sifive-uart.c
+++ b/lib/utils/serial/sifive-uart.c
@@ -48,12 +48,12 @@ static inline unsigned int uart_min_clk_divisor(uint64_t in_freq,
 						uint64_t max_target_hz)
 {
 	uint64_t quotient = (in_freq + max_target_hz - 1) / (max_target_hz);
+
 	/* Avoid underflow */
-	if (quotient == 0) {
+	if (quotient == 0)
 		return 0;
-	} else {
+	else
 		return quotient - 1;
-	}
 }
 
 static u32 get_reg(u32 num)
@@ -77,8 +77,10 @@ static void sifive_uart_putc(char ch)
 static int sifive_uart_getc(void)
 {
 	u32 ret = get_reg(UART_REG_RXFIFO);
+
 	if (!(ret & UART_RXFIFO_EMPTY))
 		return ret & UART_RXFIFO_DATA;
+
 	return -1;
 }
 
@@ -95,12 +97,15 @@ int sifive_uart_init(unsigned long base, u32 in_freq, u32 baudrate)
 	uart_baudrate = baudrate;
 
 	/* Configure baudrate */
-	if (in_freq)
+	if (in_freq && baudrate)
 		set_reg(UART_REG_DIV, uart_min_clk_divisor(in_freq, baudrate));
+
 	/* Disable interrupts */
 	set_reg(UART_REG_IE, 0);
+
 	/* Enable TX */
 	set_reg(UART_REG_TXCTRL, UART_TXCTRL_TXEN);
+
 	/* Enable Rx */
 	set_reg(UART_REG_RXCTRL, UART_RXCTRL_RXEN);
 
diff --git a/lib/utils/serial/uart8250.c b/lib/utils/serial/uart8250.c
index 38ea11a..99bf1bf 100644
--- a/lib/utils/serial/uart8250.c
+++ b/lib/utils/serial/uart8250.c
@@ -93,7 +93,7 @@ static struct sbi_console_device uart8250_console = {
 int uart8250_init(unsigned long base, u32 in_freq, u32 baudrate, u32 reg_shift,
 		  u32 reg_width, u32 reg_offset)
 {
-	u16 bdiv;
+	u16 bdiv = 0;
 
 	uart8250_base      = (volatile char *)base + reg_offset;
 	uart8250_reg_shift = reg_shift;
@@ -101,7 +101,10 @@ int uart8250_init(unsigned long base, u32 in_freq, u32 baudrate, u32 reg_shift,
 	uart8250_in_freq   = in_freq;
 	uart8250_baudrate  = baudrate;
 
-	bdiv = (uart8250_in_freq + 8 * uart8250_baudrate) / (16 * uart8250_baudrate);
+	if (uart8250_baudrate) {
+		bdiv = (uart8250_in_freq + 8 * uart8250_baudrate) /
+		       (16 * uart8250_baudrate);
+	}
 
 	/* Disable all interrupts */
 	set_reg(UART_IER_OFFSET, 0x00);
diff --git a/lib/utils/sys/Kconfig b/lib/utils/sys/Kconfig
new file mode 100644
index 0000000..ee85b1a
--- /dev/null
+++ b/lib/utils/sys/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "System Device Support"
+
+config SYS_HTIF
+	bool "Host transfere interface (HTIF) support"
+	default n
+
+config SYS_SIFIVE_TEST
+	bool "SiFive test support"
+	default n
+
+endmenu
diff --git a/lib/utils/sys/objects.mk b/lib/utils/sys/objects.mk
index 06be322..9f67aee 100644
--- a/lib/utils/sys/objects.mk
+++ b/lib/utils/sys/objects.mk
@@ -7,5 +7,5 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += sys/htif.o
-libsbiutils-objs-y += sys/sifive_test.o
+libsbiutils-objs-$(CONFIG_SYS_HTIF) += sys/htif.o
+libsbiutils-objs-$(CONFIG_SYS_SIFIVE_TEST) += sys/sifive_test.o
diff --git a/lib/utils/timer/Kconfig b/lib/utils/timer/Kconfig
new file mode 100644
index 0000000..ba211b6
--- /dev/null
+++ b/lib/utils/timer/Kconfig
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+menu "Timer Device Support"
+
+config FDT_TIMER
+	bool "FDT based timer drivers"
+	depends on FDT
+	default n
+
+if FDT_TIMER
+
+config FDT_TIMER_MTIMER
+	bool "ACLINT MTIMER FDT driver"
+	select TIMER_MTIMER
+	default n
+
+config FDT_TIMER_PLMT
+	bool "Andes PLMT FDT driver"
+	select TIMER_PLMT
+	default n
+
+endif
+
+config TIMER_MTIMER
+	bool "ACLINT MTIMER support"
+	default n
+
+config TIMER_PLMT
+	bool "Andes PLMT support"
+	default n
+
+endmenu
diff --git a/lib/utils/timer/aclint_mtimer.c b/lib/utils/timer/aclint_mtimer.c
index a957b1c..1846a9a 100644
--- a/lib/utils/timer/aclint_mtimer.c
+++ b/lib/utils/timer/aclint_mtimer.c
@@ -142,34 +142,6 @@ int aclint_mtimer_warm_init(void)
 	return 0;
 }
 
-static int aclint_mtimer_add_regions(unsigned long addr, unsigned long size)
-{
-#define MTIMER_ADD_REGION_ALIGN		0x1000
-	int rc;
-	unsigned long pos, end, rsize;
-	struct sbi_domain_memregion reg;
-
-	pos = addr;
-	end = addr + size;
-	while (pos < end) {
-		rsize = pos & (MTIMER_ADD_REGION_ALIGN - 1);
-		if (rsize)
-			rsize = 1UL << sbi_ffs(pos);
-		else
-			rsize = ((end - pos) < MTIMER_ADD_REGION_ALIGN) ?
-				(end - pos) : MTIMER_ADD_REGION_ALIGN;
-
-		sbi_domain_memregion_init(pos, rsize,
-					  SBI_DOMAIN_MEMREGION_MMIO, &reg);
-		rc = sbi_domain_root_add_memregion(&reg);
-		if (rc)
-			return rc;
-		pos += rsize;
-	}
-
-	return 0;
-}
-
 int aclint_mtimer_cold_init(struct aclint_mtimer_data *mt,
 			    struct aclint_mtimer_data *reference)
 {
@@ -177,10 +149,10 @@ int aclint_mtimer_cold_init(struct aclint_mtimer_data *mt,
 	int rc;
 
 	/* Sanity checks */
-	if (!mt || !mt->mtime_size ||
+	if (!mt ||
 	    (mt->hart_count && !mt->mtimecmp_size) ||
-	    (mt->mtime_addr & (ACLINT_MTIMER_ALIGN - 1)) ||
-	    (mt->mtime_size & (ACLINT_MTIMER_ALIGN - 1)) ||
+	    (mt->mtime_size && (mt->mtime_addr & (ACLINT_MTIMER_ALIGN - 1))) ||
+	    (mt->mtime_size && (mt->mtime_size & (ACLINT_MTIMER_ALIGN - 1))) ||
 	    (mt->mtimecmp_addr & (ACLINT_MTIMER_ALIGN - 1)) ||
 	    (mt->mtimecmp_size & (ACLINT_MTIMER_ALIGN - 1)) ||
 	    (mt->first_hartid >= SBI_HARTMASK_MAX_BITS) ||
@@ -206,25 +178,36 @@ int aclint_mtimer_cold_init(struct aclint_mtimer_data *mt,
 	for (i = 0; i < mt->hart_count; i++)
 		mtimer_hartid2data[mt->first_hartid + i] = mt;
 
+	if (!mt->mtime_size) {
+		/* Disable reading mtime when mtime is not available */
+		mtimer.timer_value = NULL;
+	}
+
 	/* Add MTIMER regions to the root domain */
 	if (mt->mtime_addr == (mt->mtimecmp_addr + mt->mtimecmp_size)) {
-		rc = aclint_mtimer_add_regions(mt->mtimecmp_addr,
-					mt->mtime_size + mt->mtimecmp_size);
+		rc = sbi_domain_root_add_memrange(mt->mtimecmp_addr,
+					mt->mtime_size + mt->mtimecmp_size,
+					MTIMER_REGION_ALIGN,
+					SBI_DOMAIN_MEMREGION_MMIO);
 		if (rc)
 			return rc;
 	} else if (mt->mtimecmp_addr == (mt->mtime_addr + mt->mtime_size)) {
-		rc = aclint_mtimer_add_regions(mt->mtime_addr,
-					mt->mtime_size + mt->mtimecmp_size);
+		rc = sbi_domain_root_add_memrange(mt->mtime_addr,
+					mt->mtime_size + mt->mtimecmp_size,
+					MTIMER_REGION_ALIGN,
+					SBI_DOMAIN_MEMREGION_MMIO);
 		if (rc)
 			return rc;
 	} else {
-		rc = aclint_mtimer_add_regions(mt->mtime_addr,
-						mt->mtime_size);
+		rc = sbi_domain_root_add_memrange(mt->mtime_addr,
+						mt->mtime_size, MTIMER_REGION_ALIGN,
+						SBI_DOMAIN_MEMREGION_MMIO);
 		if (rc)
 			return rc;
 
-		rc = aclint_mtimer_add_regions(mt->mtimecmp_addr,
-						mt->mtimecmp_size);
+		rc = sbi_domain_root_add_memrange(mt->mtimecmp_addr,
+						mt->mtimecmp_size, MTIMER_REGION_ALIGN,
+						SBI_DOMAIN_MEMREGION_MMIO);
 		if (rc)
 			return rc;
 	}
diff --git a/lib/utils/timer/andes_plmt.c b/lib/utils/timer/andes_plmt.c
new file mode 100644
index 0000000..94a86cc
--- /dev/null
+++ b/lib/utils/timer/andes_plmt.c
@@ -0,0 +1,104 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#include <sbi/riscv_asm.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_timer.h>
+#include <sbi_utils/timer/andes_plmt.h>
+
+struct plmt_data plmt;
+
+static u64 plmt_timer_value(void)
+{
+#if __riscv_xlen == 64
+	return readq_relaxed(plmt.time_val);
+#else
+	u32 lo, hi;
+
+	do {
+		hi = readl_relaxed((void *)plmt.time_val + 0x04);
+		lo = readl_relaxed(plmt.time_val);
+	} while (hi != readl_relaxed((void *)plmt.time_val + 0x04));
+
+	return ((u64)hi << 32) | (u64)lo;
+#endif
+}
+
+static void plmt_timer_event_stop(void)
+{
+	u32 target_hart = current_hartid();
+
+	if (plmt.hart_count <= target_hart)
+		ebreak();
+
+	/* Clear PLMT Time Compare */
+#if __riscv_xlen == 64
+	writeq_relaxed(-1ULL, &plmt.time_cmp[target_hart]);
+#else
+	writel_relaxed(-1UL, &plmt.time_cmp[target_hart]);
+	writel_relaxed(-1UL, (void *)(&plmt.time_cmp[target_hart]) + 0x04);
+#endif
+}
+
+static void plmt_timer_event_start(u64 next_event)
+{
+	u32 target_hart = current_hartid();
+
+	if (plmt.hart_count <= target_hart)
+		ebreak();
+
+	/* Program PLMT Time Compare */
+#if __riscv_xlen == 64
+	writeq_relaxed(next_event, &plmt.time_cmp[target_hart]);
+#else
+	u32 mask = -1UL;
+
+	writel_relaxed(next_event & mask, &plmt.time_cmp[target_hart]);
+	writel_relaxed(next_event >> 32,
+		       (void *)(&plmt.time_cmp[target_hart]) + 0x04);
+#endif
+}
+
+static struct sbi_timer_device plmt_timer = {
+	.name		   = "andes_plmt",
+	.timer_freq	   = DEFAULT_AE350_PLMT_FREQ,
+	.timer_value	   = plmt_timer_value,
+	.timer_event_start = plmt_timer_event_start,
+	.timer_event_stop  = plmt_timer_event_stop
+};
+
+int plmt_cold_timer_init(struct plmt_data *plmt)
+{
+	int rc;
+
+	/* Add PLMT region to the root domain */
+	rc = sbi_domain_root_add_memrange(
+		(unsigned long)plmt->time_val, plmt->size, PLMT_REGION_ALIGN,
+		SBI_DOMAIN_MEMREGION_MMIO | SBI_DOMAIN_MEMREGION_READABLE);
+	if (rc)
+		return rc;
+
+	plmt_timer.timer_freq = plmt->timer_freq;
+
+	sbi_timer_set_device(&plmt_timer);
+
+	return 0;
+}
+
+int plmt_warm_timer_init(void)
+{
+	if (!plmt.time_val)
+		return SBI_ENODEV;
+
+	plmt_timer_event_stop();
+
+	return 0;
+}
diff --git a/lib/utils/timer/fdt_timer_mtimer.c b/lib/utils/timer/fdt_timer_mtimer.c
index 7b8546b..5244f98 100644
--- a/lib/utils/timer/fdt_timer_mtimer.c
+++ b/lib/utils/timer/fdt_timer_mtimer.c
@@ -18,6 +18,7 @@
 struct timer_mtimer_quirks {
 	unsigned int	mtime_offset;
 	bool		has_64bit_mmio;
+	bool		without_mtime;
 };
 
 static unsigned long mtimer_count = 0;
@@ -53,12 +54,16 @@ static int timer_mtimer_cold_init(void *fdt, int nodeoff,
 		/* Set CLINT addresses */
 		mt->mtimecmp_addr = addr[0] + ACLINT_DEFAULT_MTIMECMP_OFFSET;
 		mt->mtimecmp_size = ACLINT_DEFAULT_MTIMECMP_SIZE;
-		mt->mtime_addr = addr[0] + ACLINT_DEFAULT_MTIME_OFFSET;
-		mt->mtime_size = size[0] - mt->mtimecmp_size;
-		/* Adjust MTIMER address and size for CLINT device */
-		mt->mtime_addr += quirks->mtime_offset;
+		if (!quirks->without_mtime) {
+			mt->mtime_addr = addr[0] + ACLINT_DEFAULT_MTIME_OFFSET;
+			mt->mtime_size = size[0] - mt->mtimecmp_size;
+			/* Adjust MTIMER address and size for CLINT device */
+			mt->mtime_addr += quirks->mtime_offset;
+			mt->mtime_size -= quirks->mtime_offset;
+		} else {
+			mt->mtime_addr = mt->mtime_size = 0;
+		}
 		mt->mtimecmp_addr += quirks->mtime_offset;
-		mt->mtime_size -= quirks->mtime_offset;
 		/* Apply additional CLINT quirks */
 		mt->has_64bit_mmio = quirks->has_64bit_mmio;
 	} else { /* RISC-V ACLINT MTIMER */
@@ -70,12 +75,17 @@ static int timer_mtimer_cold_init(void *fdt, int nodeoff,
 	}
 
 	/* Check if MTIMER device has shared MTIME address */
-	mt->has_shared_mtime = false;
-	for (i = 0; i < mtimer_count; i++) {
-		if (mtimer[i].mtime_addr == mt->mtime_addr) {
-			mt->has_shared_mtime = true;
-			break;
+	if (mt->mtime_size) {
+		mt->has_shared_mtime = false;
+		for (i = 0; i < mtimer_count; i++) {
+			if (mtimer[i].mtime_addr == mt->mtime_addr) {
+				mt->has_shared_mtime = true;
+				break;
+			}
 		}
+	} else {
+		/* Assume shared time CSR */
+		mt->has_shared_mtime = true;
 	}
 
 	/* Initialize the MTIMER device */
@@ -114,9 +124,15 @@ static const struct timer_mtimer_quirks sifive_clint_quirks = {
 	.has_64bit_mmio	= true,
 };
 
+static const struct timer_mtimer_quirks thead_clint_quirks = {
+	.mtime_offset	= CLINT_MTIMER_OFFSET,
+	.without_mtime  = true,
+};
+
 static const struct fdt_match timer_mtimer_match[] = {
 	{ .compatible = "riscv,clint0", .data = &sifive_clint_quirks },
 	{ .compatible = "sifive,clint0", .data = &sifive_clint_quirks },
+	{ .compatible = "thead,c900-clint", .data = &thead_clint_quirks },
 	{ .compatible = "riscv,aclint-mtimer" },
 	{ },
 };
diff --git a/lib/utils/timer/fdt_timer_plmt.c b/lib/utils/timer/fdt_timer_plmt.c
new file mode 100644
index 0000000..e8be91b
--- /dev/null
+++ b/lib/utils/timer/fdt_timer_plmt.c
@@ -0,0 +1,51 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/timer/fdt_timer.h>
+#include <sbi_utils/timer/andes_plmt.h>
+
+extern struct plmt_data plmt;
+
+static int fdt_plmt_cold_timer_init(void *fdt, int nodeoff,
+				    const struct fdt_match *match)
+{
+	int rc;
+	unsigned long plmt_base;
+
+	rc = fdt_parse_plmt_node(fdt, nodeoff, &plmt_base, &plmt.size,
+				 &plmt.hart_count);
+	if (rc)
+		return rc;
+
+	plmt.time_val = (u64 *)plmt_base;
+	plmt.time_cmp = (u64 *)(plmt_base + 0x8);
+
+	rc = fdt_parse_timebase_frequency(fdt, &plmt.timer_freq);
+	if (rc)
+		return rc;
+
+	rc = plmt_cold_timer_init(&plmt);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static const struct fdt_match timer_plmt_match[] = {
+	{ .compatible = "andestech,plmt0" },
+	{},
+};
+
+struct fdt_timer fdt_timer_plmt = {
+	.match_table = timer_plmt_match,
+	.cold_init   = fdt_plmt_cold_timer_init,
+	.warm_init   = plmt_warm_timer_init,
+	.exit	     = NULL,
+};
diff --git a/lib/utils/timer/objects.mk b/lib/utils/timer/objects.mk
index bc4073d..9360a76 100644
--- a/lib/utils/timer/objects.mk
+++ b/lib/utils/timer/objects.mk
@@ -7,10 +7,14 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
-libsbiutils-objs-y += timer/aclint_mtimer.o
+libsbiutils-objs-$(CONFIG_TIMER_MTIMER) += timer/aclint_mtimer.o
+libsbiutils-objs-$(CONFIG_TIMER_PLMT) += timer/andes_plmt.o
 
-libsbiutils-objs-y += timer/fdt_timer.o
-libsbiutils-objs-y += timer/fdt_timer_drivers.o
+libsbiutils-objs-$(CONFIG_FDT_TIMER) += timer/fdt_timer.o
+libsbiutils-objs-$(CONFIG_FDT_TIMER) += timer/fdt_timer_drivers.o
 
-carray-fdt_timer_drivers-y += fdt_timer_mtimer
-libsbiutils-objs-y += timer/fdt_timer_mtimer.o
+carray-fdt_timer_drivers-$(CONFIG_FDT_TIMER_MTIMER) += fdt_timer_mtimer
+libsbiutils-objs-$(CONFIG_FDT_TIMER_MTIMER) += timer/fdt_timer_mtimer.o
+
+carray-fdt_timer_drivers-$(CONFIG_FDT_TIMER_PLMT) += fdt_timer_plmt
+libsbiutils-objs-$(CONFIG_FDT_TIMER_PLMT) += timer/fdt_timer_plmt.o
diff --git a/platform/andes/ae350/cache.c b/platform/andes/ae350/cache.c
deleted file mode 100644
index af724c5..0000000
--- a/platform/andes/ae350/cache.c
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2020 Andes Technology Corporation
- *
- * Authors:
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-#include <sbi/riscv_asm.h>
-#include <sbi/riscv_io.h>
-#include <sbi/sbi_types.h>
-#include "platform.h"
-
-uintptr_t mcall_set_mcache_ctl(unsigned long input)
-{
-	csr_clear(CSR_MCACHECTL, V5_MCACHE_CTL_MASK);
-	csr_write(CSR_MCACHECTL, input);
-	return 0;
-}
-
-uintptr_t mcall_set_mmisc_ctl(unsigned long input)
-{
-	csr_clear(CSR_MMISCCTL, V5_MMISC_CTL_MASK);
-	csr_write(CSR_MMISCCTL, input);
-	return 0;
-}
-
-uintptr_t mcall_icache_op(unsigned int enable)
-{
-	if (enable) {
-		csr_set(CSR_MCACHECTL, V5_MCACHE_CTL_IC_EN);
-	} else {
-		csr_clear(CSR_MCACHECTL, V5_MCACHE_CTL_IC_EN);
-		asm volatile("fence.i\n\t");
-	}
-	return 0;
-}
-
-uintptr_t mcall_dcache_op(unsigned int enable)
-{
-	if (enable) {
-		csr_set(CSR_MCACHECTL, V5_MCACHE_CTL_DC_EN);
-	} else {
-		csr_clear(CSR_MCACHECTL, V5_MCACHE_CTL_DC_EN);
-		csr_write(CSR_MCCTLCOMMAND, V5_UCCTL_L1D_WBINVAL_ALL);
-	}
-	return 0;
-}
-
-uintptr_t mcall_l1_cache_i_prefetch_op(unsigned long enable)
-{
-	if (enable) {
-		csr_set(CSR_MCACHECTL, V5_MCACHE_CTL_L1I_PREFETCH_EN);
-	} else {
-		csr_clear(CSR_MCACHECTL, V5_MCACHE_CTL_L1I_PREFETCH_EN);
-	}
-	return 0;
-}
-
-uintptr_t mcall_l1_cache_d_prefetch_op(unsigned long enable)
-{
-	if (enable) {
-		csr_set(CSR_MCACHECTL, V5_MCACHE_CTL_L1D_PREFETCH_EN);
-	} else {
-		csr_clear(CSR_MCACHECTL, V5_MCACHE_CTL_L1D_PREFETCH_EN);
-	}
-	return 0;
-}
-
-uintptr_t mcall_non_blocking_load_store(unsigned long enable)
-{
-	if (enable) {
-		csr_set(CSR_MCACHECTL, V5_MMISC_CTL_NON_BLOCKING_EN);
-	} else {
-		csr_clear(CSR_MCACHECTL, V5_MMISC_CTL_NON_BLOCKING_EN);
-	}
-	return 0;
-}
-
-uintptr_t mcall_write_around(unsigned long enable)
-{
-	if (enable) {
-		csr_set(CSR_MCACHECTL, V5_MCACHE_CTL_DC_WAROUND_1_EN);
-	} else {
-		csr_clear(CSR_MCACHECTL, V5_MCACHE_CTL_DC_WAROUND_1_EN);
-	}
-	return 0;
-}
diff --git a/platform/andes/ae350/cache.h b/platform/andes/ae350/cache.h
deleted file mode 100644
index e1c1826..0000000
--- a/platform/andes/ae350/cache.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2020 Andes Technology Corporation
- *
- * Authors:
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-uintptr_t mcall_set_mcache_ctl(unsigned long input);
-uintptr_t mcall_set_mmisc_ctl(unsigned long input);
-uintptr_t mcall_icache_op(unsigned int enable);
-uintptr_t mcall_dcache_op(unsigned int enable);
-uintptr_t mcall_l1_cache_i_prefetch_op(unsigned long enable);
-uintptr_t mcall_l1_cache_d_prefetch_op(unsigned long enable);
-uintptr_t mcall_non_blocking_load_store(unsigned long enable);
-uintptr_t mcall_write_around(unsigned long enable);
diff --git a/platform/andes/ae350/config.mk b/platform/andes/ae350/config.mk
deleted file mode 100644
index f555ef5..0000000
--- a/platform/andes/ae350/config.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2019 Andes Technology Corporation
-#
-# Authors:
-#   Zong Li <zong@andestech.com>
-#   Nylon Chen <nylon7@andestech.com>
-
-# Compiler flags
-platform-cppflags-y =
-platform-cflags-y =
-platform-asflags-y =
-platform-ldflags-y =
-
-# Blobs to build
-FW_TEXT_START=0x00000000
-
-FW_DYNAMIC=y
-
-FW_JUMP=y
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-  FW_JUMP_ADDR=0x400000
-else
-  FW_JUMP_ADDR=0x200000
-endif
-FW_JUMP_FDT_ADDR=0x2000000
-
-FW_PAYLOAD=y
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-  FW_PAYLOAD_OFFSET=0x400000
-else
-  FW_PAYLOAD_OFFSET=0x200000
-endif
-
-FW_PAYLOAD_FDT_ADDR=0x2000000
diff --git a/platform/andes/ae350/objects.mk b/platform/andes/ae350/objects.mk
deleted file mode 100644
index 5369677..0000000
--- a/platform/andes/ae350/objects.mk
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2019 Andes Technology Corporation
-#
-# Authors:
-#   Zong Li <zong@andestech.com>
-#   Nylon Chen <nylon7@andestech.com>
-#
-
-platform-objs-y += cache.o platform.o plicsw.o plmt.o
diff --git a/platform/andes/ae350/platform.c b/platform/andes/ae350/platform.c
deleted file mode 100644
index 6bd0a69..0000000
--- a/platform/andes/ae350/platform.c
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 Andes Technology Corporation
- *
- * Authors:
- *   Zong Li <zong@andestech.com>
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-#include <sbi/riscv_asm.h>
-#include <sbi/riscv_encoding.h>
-#include <sbi/sbi_console.h>
-#include <sbi/sbi_const.h>
-#include <sbi/sbi_ipi.h>
-#include <sbi/sbi_platform.h>
-#include <sbi/sbi_trap.h>
-#include <sbi_utils/fdt/fdt_helper.h>
-#include <sbi_utils/fdt/fdt_fixup.h>
-#include <sbi_utils/irqchip/plic.h>
-#include <sbi_utils/serial/uart8250.h>
-#include "platform.h"
-#include "plicsw.h"
-#include "plmt.h"
-#include "cache.h"
-
-static struct plic_data plic = {
-	.addr = AE350_PLIC_ADDR,
-	.num_src = AE350_PLIC_NUM_SOURCES,
-};
-
-/* Platform final initialization. */
-static int ae350_final_init(bool cold_boot)
-{
-	void *fdt;
-
-	/* enable L1 cache */
-	uintptr_t mcache_ctl_val = csr_read(CSR_MCACHECTL);
-
-	if (!(mcache_ctl_val & V5_MCACHE_CTL_IC_EN))
-		mcache_ctl_val |= V5_MCACHE_CTL_IC_EN;
-	if (!(mcache_ctl_val & V5_MCACHE_CTL_DC_EN))
-		mcache_ctl_val |= V5_MCACHE_CTL_DC_EN;
-	if (!(mcache_ctl_val & V5_MCACHE_CTL_CCTL_SUEN))
-		mcache_ctl_val |= V5_MCACHE_CTL_CCTL_SUEN;
-	csr_write(CSR_MCACHECTL, mcache_ctl_val);
-
-	/* enable L2 cache */
-	uint32_t *l2c_ctl_base = (void *)AE350_L2C_ADDR + V5_L2C_CTL_OFFSET;
-	uint32_t l2c_ctl_val = *l2c_ctl_base;
-
-	if (!(l2c_ctl_val & V5_L2C_CTL_ENABLE_MASK))
-		l2c_ctl_val |= V5_L2C_CTL_ENABLE_MASK;
-	*l2c_ctl_base = l2c_ctl_val;
-
-	if (!cold_boot)
-		return 0;
-
-	fdt = fdt_get_address();
-	fdt_fixups(fdt);
-
-	return 0;
-}
-
-/* Initialize the platform console. */
-static int ae350_console_init(void)
-{
-	return uart8250_init(AE350_UART_ADDR,
-			     AE350_UART_FREQUENCY,
-			     AE350_UART_BAUDRATE,
-			     AE350_UART_REG_SHIFT,
-			     AE350_UART_REG_WIDTH,
-			     AE350_UART_REG_OFFSET);
-}
-
-/* Initialize the platform interrupt controller for current HART. */
-static int ae350_irqchip_init(bool cold_boot)
-{
-	u32 hartid = current_hartid();
-	int ret;
-
-	if (cold_boot) {
-		ret = plic_cold_irqchip_init(&plic);
-		if (ret)
-			return ret;
-	}
-
-	return plic_warm_irqchip_init(&plic, 2 * hartid, 2 * hartid + 1);
-}
-
-static struct sbi_ipi_device plicsw_ipi = {
-	.name = "ae350_plicsw",
-	.ipi_send = plicsw_ipi_send,
-	.ipi_clear = plicsw_ipi_clear
-};
-
-/* Initialize IPI for current HART. */
-static int ae350_ipi_init(bool cold_boot)
-{
-	int ret;
-
-	if (cold_boot) {
-		ret = plicsw_cold_ipi_init(AE350_PLICSW_ADDR,
-					   AE350_HART_COUNT);
-		if (ret)
-			return ret;
-
-		sbi_ipi_set_device(&plicsw_ipi);
-	}
-
-	return plicsw_warm_ipi_init();
-}
-
-/* Initialize platform timer for current HART. */
-static int ae350_timer_init(bool cold_boot)
-{
-	int ret;
-
-	if (cold_boot) {
-		ret = plmt_cold_timer_init(AE350_PLMT_ADDR,
-					   AE350_HART_COUNT);
-		if (ret)
-			return ret;
-	}
-
-	return plmt_warm_timer_init();
-}
-
-/* Vendor-Specific SBI handler */
-static int ae350_vendor_ext_provider(long extid, long funcid,
-	const struct sbi_trap_regs *regs, unsigned long *out_value,
-	struct sbi_trap_info *out_trap)
-{
-	int ret = 0;
-	switch (funcid) {
-	case SBI_EXT_ANDES_GET_MCACHE_CTL_STATUS:
-		*out_value = csr_read(CSR_MCACHECTL);
-		break;
-	case SBI_EXT_ANDES_GET_MMISC_CTL_STATUS:
-		*out_value = csr_read(CSR_MMISCCTL);
-		break;
-	case SBI_EXT_ANDES_SET_MCACHE_CTL:
-		ret = mcall_set_mcache_ctl(regs->a0);
-		break;
-	case SBI_EXT_ANDES_SET_MMISC_CTL:
-		ret = mcall_set_mmisc_ctl(regs->a0);
-		break;
-	case SBI_EXT_ANDES_ICACHE_OP:
-		ret = mcall_icache_op(regs->a0);
-		break;
-	case SBI_EXT_ANDES_DCACHE_OP:
-		ret = mcall_dcache_op(regs->a0);
-		break;
-	case SBI_EXT_ANDES_L1CACHE_I_PREFETCH:
-		ret = mcall_l1_cache_i_prefetch_op(regs->a0);
-		break;
-	case SBI_EXT_ANDES_L1CACHE_D_PREFETCH:
-		ret = mcall_l1_cache_d_prefetch_op(regs->a0);
-		break;
-	case SBI_EXT_ANDES_NON_BLOCKING_LOAD_STORE:
-		ret = mcall_non_blocking_load_store(regs->a0);
-		break;
-	case SBI_EXT_ANDES_WRITE_AROUND:
-		ret = mcall_write_around(regs->a0);
-		break;
-	default:
-		sbi_printf("Unsupported vendor sbi call : %ld\n", funcid);
-		asm volatile("ebreak");
-	}
-	return ret;
-}
-
-/* Platform descriptor. */
-const struct sbi_platform_operations platform_ops = {
-	.final_init = ae350_final_init,
-
-	.console_init = ae350_console_init,
-
-	.irqchip_init = ae350_irqchip_init,
-
-	.ipi_init     = ae350_ipi_init,
-
-	.timer_init	   = ae350_timer_init,
-
-	.vendor_ext_provider = ae350_vendor_ext_provider
-};
-
-const struct sbi_platform platform = {
-	.opensbi_version = OPENSBI_VERSION,
-	.platform_version = SBI_PLATFORM_VERSION(0x0, 0x01),
-	.name = "Andes AE350",
-	.features = SBI_PLATFORM_DEFAULT_FEATURES,
-	.hart_count = AE350_HART_COUNT,
-	.hart_stack_size = SBI_PLATFORM_DEFAULT_HART_STACK_SIZE,
-	.platform_ops_addr = (unsigned long)&platform_ops
-};
diff --git a/platform/andes/ae350/platform.h b/platform/andes/ae350/platform.h
deleted file mode 100644
index 9b54816..0000000
--- a/platform/andes/ae350/platform.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 Andes Technology Corporation
- *
- * Authors:
- *   Zong Li <zong@andestech.com>
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-#ifndef _AE350_PLATFORM_H_
-#define _AE350_PLATFORM_H_
-
-#define AE350_HART_COUNT		4
-
-#define AE350_PLIC_ADDR			0xe4000000
-#define AE350_PLIC_NUM_SOURCES		71
-
-#define AE350_PLICSW_ADDR		0xe6400000
-
-#define AE350_PLMT_ADDR			0xe6000000
-
-#define AE350_L2C_ADDR			0xe0500000
-
-#define AE350_UART_ADDR_OFFSET		0x20
-#define AE350_UART_ADDR			(0xf0300000 + AE350_UART_ADDR_OFFSET)
-#define AE350_UART_FREQUENCY		19660800
-#define AE350_UART_BAUDRATE		38400
-#define AE350_UART_REG_SHIFT		2
-#define AE350_UART_REG_WIDTH		0
-#define AE350_UART_REG_OFFSET		0
-
-/*Memory and Miscellaneous Registers*/
-#define CSR_MILMB		0x7c0
-#define CSR_MDLMB		0x7c1
-#define CSR_MECC_CDOE		0x7c2
-#define CSR_MNVEC		0x7c3
-#define CSR_MPFTCTL		0x7c5
-#define CSR_MCACHECTL		0x7ca
-#define CSR_MCCTLBEGINADDR	0x7cb
-#define CSR_MCCTLCOMMAND	0x7cc
-#define CSR_MCCTLDATA		0x7cc
-#define CSR_SCCTLDATA		0x9cd
-#define CSR_UCCTLBEGINADDR	0x80c
-#define CSR_MMISCCTL		0x7d0
-
-enum sbi_ext_andes_fid {
-	SBI_EXT_ANDES_GET_MCACHE_CTL_STATUS = 0,
-	SBI_EXT_ANDES_GET_MMISC_CTL_STATUS,
-	SBI_EXT_ANDES_SET_MCACHE_CTL,
-	SBI_EXT_ANDES_SET_MMISC_CTL,
-	SBI_EXT_ANDES_ICACHE_OP,
-	SBI_EXT_ANDES_DCACHE_OP,
-	SBI_EXT_ANDES_L1CACHE_I_PREFETCH,
-	SBI_EXT_ANDES_L1CACHE_D_PREFETCH,
-	SBI_EXT_ANDES_NON_BLOCKING_LOAD_STORE,
-	SBI_EXT_ANDES_WRITE_AROUND,
-};
-
-/* nds v5 mmisc_ctl register*/
-#define V5_MMISC_CTL_VEC_PLIC_OFFSET            1
-#define V5_MMISC_CTL_RVCOMPM_OFFSET             2
-#define V5_MMISC_CTL_BRPE_OFFSET                3
-#define V5_MMISC_CTL_MSA_OR_UNA_OFFSET          6
-#define V5_MMISC_CTL_NON_BLOCKING_OFFSET        8
-#define V5_MCACHE_CTL_L1I_PREFETCH_OFFSET       9
-#define V5_MCACHE_CTL_L1D_PREFETCH_OFFSET       10
-#define V5_MCACHE_CTL_DC_WAROUND_OFFSET_1       13
-#define V5_MCACHE_CTL_DC_WAROUND_OFFSET_2       14
-
-#define V5_MMISC_CTL_VEC_PLIC_EN        (1UL << V5_MMISC_CTL_VEC_PLIC_OFFSET)
-#define V5_MMISC_CTL_RVCOMPM_EN         (1UL << V5_MMISC_CTL_RVCOMPM_OFFSET)
-#define V5_MMISC_CTL_BRPE_EN            (1UL << V5_MMISC_CTL_BRPE_OFFSET)
-#define V5_MMISC_CTL_MSA_OR_UNA_EN      (1UL << V5_MMISC_CTL_MSA_OR_UNA_OFFSET)
-#define V5_MMISC_CTL_NON_BLOCKING_EN    (1UL << V5_MMISC_CTL_NON_BLOCKING_OFFSET)
-#define V5_MCACHE_CTL_L1I_PREFETCH_EN   (1UL << V5_MCACHE_CTL_L1I_PREFETCH_OFFSET)
-#define V5_MCACHE_CTL_L1D_PREFETCH_EN   (1UL << V5_MCACHE_CTL_L1D_PREFETCH_OFFSET)
-#define V5_MCACHE_CTL_DC_WAROUND_1_EN   (1UL << V5_MCACHE_CTL_DC_WAROUND_OFFSET_1)
-#define V5_MCACHE_CTL_DC_WAROUND_2_EN   (1UL << V5_MCACHE_CTL_DC_WAROUND_OFFSET_2)
-
-#define V5_MMISC_CTL_MASK  (V5_MMISC_CTL_VEC_PLIC_EN | V5_MMISC_CTL_RVCOMPM_EN \
-	| V5_MMISC_CTL_BRPE_EN | V5_MMISC_CTL_MSA_OR_UNA_EN | V5_MMISC_CTL_NON_BLOCKING_EN)
-
-/* nds mcache_ctl register*/
-#define V5_MCACHE_CTL_IC_EN_OFFSET      0
-#define V5_MCACHE_CTL_DC_EN_OFFSET      1
-#define V5_MCACHE_CTL_IC_ECCEN_OFFSET   2
-#define V5_MCACHE_CTL_DC_ECCEN_OFFSET   4
-#define V5_MCACHE_CTL_IC_RWECC_OFFSET   6
-#define V5_MCACHE_CTL_DC_RWECC_OFFSET   7
-#define V5_MCACHE_CTL_CCTL_SUEN_OFFSET  8
-
-/*nds cctl command*/
-#define V5_UCCTL_L1D_WBINVAL_ALL 6
-#define V5_UCCTL_L1D_WB_ALL 7
-
-#define V5_MCACHE_CTL_IC_EN     (1UL << V5_MCACHE_CTL_IC_EN_OFFSET)
-#define V5_MCACHE_CTL_DC_EN     (1UL << V5_MCACHE_CTL_DC_EN_OFFSET)
-#define V5_MCACHE_CTL_IC_RWECC  (1UL << V5_MCACHE_CTL_IC_RWECC_OFFSET)
-#define V5_MCACHE_CTL_DC_RWECC  (1UL << V5_MCACHE_CTL_DC_RWECC_OFFSET)
-#define V5_MCACHE_CTL_CCTL_SUEN (1UL << V5_MCACHE_CTL_CCTL_SUEN_OFFSET)
-
-#define V5_MCACHE_CTL_MASK (V5_MCACHE_CTL_IC_EN | V5_MCACHE_CTL_DC_EN \
-	| V5_MCACHE_CTL_IC_RWECC | V5_MCACHE_CTL_DC_RWECC \
-	| V5_MCACHE_CTL_CCTL_SUEN | V5_MCACHE_CTL_L1I_PREFETCH_EN \
-	| V5_MCACHE_CTL_L1D_PREFETCH_EN | V5_MCACHE_CTL_DC_WAROUND_1_EN \
-	| V5_MCACHE_CTL_DC_WAROUND_2_EN)
-
-#define V5_L2C_CTL_OFFSET           0x8
-#define V5_L2C_CTL_ENABLE_OFFSET    0
-#define V5_L2C_CTL_IPFDPT_OFFSET    3
-#define V5_L2C_CTL_DPFDPT_OFFSET    5
-#define V5_L2C_CTL_TRAMOCTL_OFFSET  8
-#define V5_L2C_CTL_TRAMICTL_OFFSET  10
-#define V5_L2C_CTL_DRAMOCTL_OFFSET  11
-#define V5_L2C_CTL_DRAMICTL_OFFSET  13
-
-#define V5_L2C_CTL_ENABLE_MASK      (1UL << V5_L2C_CTL_ENABLE_OFFSET)
-#define V5_L2C_CTL_IPFDPT_MASK      (3UL << V5_L2C_CTL_IPFDPT_OFFSET)
-#define V5_L2C_CTL_DPFDPT_MASK      (3UL << V5_L2C_CTL_DPFDPT_OFFSET)
-#define V5_L2C_CTL_TRAMOCTL_MASK    (3UL << V5_L2C_CTL_TRAMOCTL_OFFSET)
-#define V5_L2C_CTL_TRAMICTL_MASK    (1UL << V5_L2C_CTL_TRAMICTL_OFFSET)
-#define V5_L2C_CTL_DRAMOCTL_MASK    (3UL << V5_L2C_CTL_DRAMOCTL_OFFSET)
-#define V5_L2C_CTL_DRAMICTL_MASK    (1UL << V5_L2C_CTL_DRAMICTL_OFFSET)
-
-#endif /* _AE350_PLATFORM_H_ */
diff --git a/platform/andes/ae350/plicsw.c b/platform/andes/ae350/plicsw.c
deleted file mode 100644
index edbbb11..0000000
--- a/platform/andes/ae350/plicsw.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 Andes Technology Corporation
- *
- * Authors:
- *   Zong Li <zong@andestech.com>
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-#include <sbi/riscv_asm.h>
-#include <sbi/riscv_io.h>
-#include <sbi/sbi_types.h>
-#include "plicsw.h"
-#include "platform.h"
-
-static u32 plicsw_ipi_hart_count;
-static struct plicsw plicsw_dev[AE350_HART_COUNT];
-
-static inline void plicsw_claim(void)
-{
-	u32 source_hart = current_hartid();
-
-	plicsw_dev[source_hart].source_id =
-		readl(plicsw_dev[source_hart].plicsw_claim);
-}
-
-static inline void plicsw_complete(void)
-{
-	u32 source_hart = current_hartid();
-	u32 source = plicsw_dev[source_hart].source_id;
-
-	writel(source, plicsw_dev[source_hart].plicsw_claim);
-}
-
-static inline void plic_sw_pending(u32 target_hart)
-{
-	/*
-	 * The pending array registers are w1s type.
-	 * IPI pending array mapping as following:
-	 *
-	 * Pending array start address: base + 0x1000
-	 * -------------------------------------
-	 * | hart 3 | hart 2 | hart 1 | hart 0 |
-	 * -------------------------------------
-	 * Each hart X can send IPI to another hart by setting the
-	 * corresponding bit in hart X own region(see the below).
-	 *
-	 * In each hart region:
-	 * -----------------------------------------------
-	 * | bit 7 | bit 6 | bit 5 | bit 4 | ... | bit 0 |
-	 * -----------------------------------------------
-	 * The bit 7 is used to send IPI to hart 0
-	 * The bit 6 is used to send IPI to hart 1
-	 * The bit 5 is used to send IPI to hart 2
-	 * The bit 4 is used to send IPI to hart 3
-	 */
-	u32 source_hart = current_hartid();
-	u32 target_offset = (PLICSW_PENDING_PER_HART - 1) - target_hart;
-	u32 per_hart_offset = PLICSW_PENDING_PER_HART * source_hart;
-	u32 val = 1 << target_offset << per_hart_offset;
-
-	writel(val, plicsw_dev[source_hart].plicsw_pending);
-}
-
-void plicsw_ipi_send(u32 target_hart)
-{
-	if (plicsw_ipi_hart_count <= target_hart)
-		return;
-
-	/* Set PLICSW IPI */
-	plic_sw_pending(target_hart);
-}
-
-void plicsw_ipi_clear(u32 target_hart)
-{
-	if (plicsw_ipi_hart_count <= target_hart)
-		return;
-
-	/* Clear PLICSW IPI */
-	plicsw_claim();
-	plicsw_complete();
-}
-
-int plicsw_warm_ipi_init(void)
-{
-	u32 hartid = current_hartid();
-
-	if (!plicsw_dev[hartid].plicsw_pending
-	    && !plicsw_dev[hartid].plicsw_enable
-	    && !plicsw_dev[hartid].plicsw_claim)
-		return -1;
-
-	/* Clear PLICSW IPI */
-	plicsw_ipi_clear(hartid);
-
-	return 0;
-}
-
-int plicsw_cold_ipi_init(unsigned long base, u32 hart_count)
-{
-	/* Setup source priority */
-	uint32_t *priority = (void *)base + PLICSW_PRIORITY_BASE;
-
-	for (int i = 0; i < AE350_HART_COUNT * PLICSW_PENDING_PER_HART; i++)
-		writel(1, &priority[i]);
-
-	/* Setup target enable */
-	uint32_t enable_mask = PLICSW_HART_MASK;
-
-	for (int i = 0; i < AE350_HART_COUNT; i++) {
-		uint32_t *enable = (void *)base + PLICSW_ENABLE_BASE
-			+ PLICSW_ENABLE_PER_HART * i;
-		writel(enable_mask, &enable[0]);
-		enable_mask >>= 1;
-	}
-
-	/* Figure-out PLICSW IPI register address */
-	plicsw_ipi_hart_count = hart_count;
-
-	for (u32 hartid = 0; hartid < AE350_HART_COUNT; hartid++) {
-		plicsw_dev[hartid].source_id = 0;
-		plicsw_dev[hartid].plicsw_pending =
-			(void *)base
-			+ PLICSW_PENDING_BASE
-			+ ((hartid / 4) * 4);
-		plicsw_dev[hartid].plicsw_enable  =
-			(void *)base
-			+ PLICSW_ENABLE_BASE
-			+ PLICSW_ENABLE_PER_HART * hartid;
-		plicsw_dev[hartid].plicsw_claim   =
-			(void *)base
-			+ PLICSW_CONTEXT_BASE
-			+ PLICSW_CONTEXT_CLAIM
-			+ PLICSW_CONTEXT_PER_HART * hartid;
-	}
-
-	return 0;
-}
diff --git a/platform/andes/ae350/plicsw.h b/platform/andes/ae350/plicsw.h
deleted file mode 100644
index 58728f6..0000000
--- a/platform/andes/ae350/plicsw.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 Andes Technology Corporation
- *
- * Authors:
- *   Zong Li <zong@andestech.com>
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-#ifndef _AE350_PLICSW_H_
-#define _AE350_PLICSW_H_
-
-#define PLICSW_PRIORITY_BASE		0x4
-
-#define PLICSW_PENDING_BASE		0x1000
-#define PLICSW_PENDING_PER_HART		0x8
-
-#define PLICSW_ENABLE_BASE		0x2000
-#define PLICSW_ENABLE_PER_HART		0x80
-
-#define PLICSW_CONTEXT_BASE		0x200000
-#define PLICSW_CONTEXT_PER_HART		0x1000
-#define PLICSW_CONTEXT_CLAIM		0x4
-
-#define PLICSW_HART_MASK		0x80808080
-
-struct plicsw {
-	u32 source_id;
-
-	volatile uint32_t *plicsw_pending;
-	volatile uint32_t *plicsw_enable;
-	volatile uint32_t *plicsw_claim;
-};
-
-void plicsw_ipi_send(u32 target_hart);
-
-void plicsw_ipi_clear(u32 target_hart);
-
-int plicsw_warm_ipi_init(void);
-
-int plicsw_cold_ipi_init(unsigned long base, u32 hart_count);
-
-#endif /* _AE350_PLICSW_H_ */
diff --git a/platform/andes/ae350/plmt.c b/platform/andes/ae350/plmt.c
deleted file mode 100644
index 54dcb94..0000000
--- a/platform/andes/ae350/plmt.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 Andes Technology Corporation
- *
- * Authors:
- *   Zong Li <zong@andestech.com>
- *   Nylon Chen <nylon7@andestech.com>
- */
-
-#include <sbi/riscv_asm.h>
-#include <sbi/riscv_io.h>
-#include <sbi/sbi_timer.h>
-
-static u32 plmt_time_hart_count;
-static volatile void *plmt_time_base;
-static volatile u64 *plmt_time_val;
-static volatile u64 *plmt_time_cmp;
-
-static u64 plmt_timer_value(void)
-{
-#if __riscv_xlen == 64
-	return readq_relaxed(plmt_time_val);
-#else
-	u32 lo, hi;
-
-	do {
-		hi = readl_relaxed((void *)plmt_time_val + 0x04);
-		lo = readl_relaxed(plmt_time_val);
-	} while (hi != readl_relaxed((void *)plmt_time_val + 0x04));
-
-	return ((u64)hi << 32) | (u64)lo;
-#endif
-}
-
-static void plmt_timer_event_stop(void)
-{
-	u32 target_hart = current_hartid();
-
-	if (plmt_time_hart_count <= target_hart)
-		return;
-
-	/* Clear PLMT Time Compare */
-#if __riscv_xlen == 64
-	writeq_relaxed(-1ULL, &plmt_time_cmp[target_hart]);
-#else
-	writel_relaxed(-1UL, &plmt_time_cmp[target_hart]);
-	writel_relaxed(-1UL, (void *)(&plmt_time_cmp[target_hart]) + 0x04);
-#endif
-}
-
-static void plmt_timer_event_start(u64 next_event)
-{
-	u32 target_hart = current_hartid();
-
-	if (plmt_time_hart_count <= target_hart)
-		return;
-
-	/* Program PLMT Time Compare */
-#if __riscv_xlen == 64
-	writeq_relaxed(next_event, &plmt_time_cmp[target_hart]);
-#else
-	u32 mask = -1UL;
-
-	writel_relaxed(next_event & mask, &plmt_time_cmp[target_hart]);
-	writel_relaxed(next_event >> 32,
-		       (void *)(&plmt_time_cmp[target_hart]) + 0x04);
-#endif
-
-}
-
-static struct sbi_timer_device plmt_timer = {
-	.name = "ae350_plmt",
-	.timer_value = plmt_timer_value,
-	.timer_event_start = plmt_timer_event_start,
-	.timer_event_stop = plmt_timer_event_stop
-};
-
-int plmt_warm_timer_init(void)
-{
-	u32 target_hart = current_hartid();
-
-	if (plmt_time_hart_count <= target_hart || !plmt_time_base)
-		return -1;
-
-	/* Clear PLMT Time Compare */
-#if __riscv_xlen == 64
-	writeq_relaxed(-1ULL, &plmt_time_cmp[target_hart]);
-#else
-	writel_relaxed(-1UL, &plmt_time_cmp[target_hart]);
-	writel_relaxed(-1UL, (void *)(&plmt_time_cmp[target_hart]) + 0x04);
-#endif
-
-	return 0;
-}
-
-int plmt_cold_timer_init(unsigned long base, u32 hart_count)
-{
-	plmt_time_hart_count = hart_count;
-	plmt_time_base	     = (void *)base;
-	plmt_time_val        = (u64 *)(plmt_time_base);
-	plmt_time_cmp        = (u64 *)(plmt_time_base + 0x8);
-
-	sbi_timer_set_device(&plmt_timer);
-
-	return 0;
-}
diff --git a/platform/andes/ae350/plmt.h b/platform/andes/ae350/plmt.h
deleted file mode 100644
index db093e0..0000000
--- a/platform/andes/ae350/plmt.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 Andes Technology Corporation
- *
- * Authors:
- *   Zong Li <zong@andestech.com>
- */
-
-#ifndef _AE350_PLMT_H_
-#define _AE350_PLMT_H_
-
-int plmt_warm_timer_init(void);
-
-int plmt_cold_timer_init(unsigned long base, u32 hart_count);
-
-#endif /* _AE350_PLMT_H_ */
diff --git a/platform/fpga/ariane/Kconfig b/platform/fpga/ariane/Kconfig
new file mode 100644
index 0000000..e1f118b
--- /dev/null
+++ b/platform/fpga/ariane/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config PLATFORM_ARIANE_FPGA
+	bool
+	select FDT
+	select IPI_MSWI
+	select IRQCHIP_PLIC
+	select SERIAL_UART8250
+	select TIMER_MTIMER
+	default y
diff --git a/platform/fpga/ariane/config.mk b/platform/fpga/ariane/config.mk
deleted file mode 100644
index 3556461..0000000
--- a/platform/fpga/ariane/config.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (C) 2019 FORTH-ICS/CARV
-#		Panagiotis Peristerakis <perister@ics.forth.gr>
-#
-
-#for more infos, check out /platform/template/config.mk
-
-PLATFORM_RISCV_XLEN = 64
-
-# Blobs to build
-FW_TEXT_START=0x80000000
-FW_JUMP=n
-
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
- # This needs to be 4MB aligned for 32-bit support
- FW_JUMP_ADDR=0x80400000
- else
- # This needs to be 2MB aligned for 64-bit support
- FW_JUMP_ADDR=0x80200000
- endif
-FW_JUMP_FDT_ADDR=0x82200000
-
-# Firmware with payload configuration.
-FW_PAYLOAD=y
-
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-# This needs to be 4MB aligned for 32-bit support
-  FW_PAYLOAD_OFFSET=0x400000
-else
-# This needs to be 2MB aligned for 64-bit support
-  FW_PAYLOAD_OFFSET=0x200000
-endif
-FW_PAYLOAD_FDT_ADDR=0x82200000
-FW_PAYLOAD_ALIGN=0x1000
diff --git a/platform/fpga/ariane/configs/defconfig b/platform/fpga/ariane/configs/defconfig
new file mode 100644
index 0000000..e69de29
diff --git a/platform/fpga/ariane/objects.mk b/platform/fpga/ariane/objects.mk
index 814e6da..83581ac 100644
--- a/platform/fpga/ariane/objects.mk
+++ b/platform/fpga/ariane/objects.mk
@@ -5,4 +5,39 @@
 #		Panagiotis Peristerakis <perister@ics.forth.gr>
 #
 
+# Compiler flags
+platform-cppflags-y =
+platform-cflags-y =
+platform-asflags-y =
+platform-ldflags-y =
+
+# Object to build
 platform-objs-y += platform.o
+
+PLATFORM_RISCV_XLEN = 64
+
+# Blobs to build
+FW_TEXT_START=0x80000000
+FW_JUMP=n
+
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+ # This needs to be 4MB aligned for 32-bit support
+ FW_JUMP_ADDR=0x80400000
+ else
+ # This needs to be 2MB aligned for 64-bit support
+ FW_JUMP_ADDR=0x80200000
+ endif
+FW_JUMP_FDT_ADDR=0x82200000
+
+# Firmware with payload configuration.
+FW_PAYLOAD=y
+
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+# This needs to be 4MB aligned for 32-bit support
+  FW_PAYLOAD_OFFSET=0x400000
+else
+# This needs to be 2MB aligned for 64-bit support
+  FW_PAYLOAD_OFFSET=0x200000
+endif
+FW_PAYLOAD_FDT_ADDR=0x82200000
+FW_PAYLOAD_ALIGN=0x1000
diff --git a/platform/fpga/openpiton/Kconfig b/platform/fpga/openpiton/Kconfig
new file mode 100644
index 0000000..bc9c86e
--- /dev/null
+++ b/platform/fpga/openpiton/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config PLATFORM_OPENPITON_FPGA
+	bool
+	select FDT
+	select IPI_MSWI
+	select IRQCHIP_PLIC
+	select SERIAL_UART8250
+	select TIMER_MTIMER
+	default y
diff --git a/platform/fpga/openpiton/config.mk b/platform/fpga/openpiton/config.mk
deleted file mode 100644
index a969b25..0000000
--- a/platform/fpga/openpiton/config.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2020 Western Digital Corporation or its affiliates.
-#
-
-#for more infos, check out /platform/template/config.mk
-
-PLATFORM_RISCV_XLEN = 64
-
-# Blobs to build
-FW_TEXT_START=0x80000000
-FW_JUMP=n
-
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
- # This needs to be 4MB aligned for 32-bit support
- FW_JUMP_ADDR=0x80400000
- else
- # This needs to be 2MB aligned for 64-bit support
- FW_JUMP_ADDR=0x80200000
- endif
-FW_JUMP_FDT_ADDR=0x82200000
-
-# Firmware with payload configuration.
-FW_PAYLOAD=y
-
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-# This needs to be 4MB aligned for 32-bit support
-  FW_PAYLOAD_OFFSET=0x400000
-else
-# This needs to be 2MB aligned for 64-bit support
-  FW_PAYLOAD_OFFSET=0x200000
-endif
-FW_PAYLOAD_FDT_ADDR=0x82200000
-FW_PAYLOAD_ALIGN=0x1000
diff --git a/platform/fpga/openpiton/configs/defconfig b/platform/fpga/openpiton/configs/defconfig
new file mode 100644
index 0000000..e69de29
diff --git a/platform/fpga/openpiton/objects.mk b/platform/fpga/openpiton/objects.mk
index 30a3c4f..c8c345a 100644
--- a/platform/fpga/openpiton/objects.mk
+++ b/platform/fpga/openpiton/objects.mk
@@ -4,4 +4,39 @@
 # Copyright (c) 2020 Western Digital Corporation or its affiliates.
 #
 
+# Compiler flags
+platform-cppflags-y =
+platform-cflags-y =
+platform-asflags-y =
+platform-ldflags-y =
+
+# Objects to build
 platform-objs-y += platform.o
+
+PLATFORM_RISCV_XLEN = 64
+
+# Blobs to build
+FW_TEXT_START=0x80000000
+FW_JUMP=n
+
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+ # This needs to be 4MB aligned for 32-bit support
+ FW_JUMP_ADDR=0x80400000
+ else
+ # This needs to be 2MB aligned for 64-bit support
+ FW_JUMP_ADDR=0x80200000
+ endif
+FW_JUMP_FDT_ADDR=0x82200000
+
+# Firmware with payload configuration.
+FW_PAYLOAD=y
+
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+# This needs to be 4MB aligned for 32-bit support
+  FW_PAYLOAD_OFFSET=0x400000
+else
+# This needs to be 2MB aligned for 64-bit support
+  FW_PAYLOAD_OFFSET=0x200000
+endif
+FW_PAYLOAD_FDT_ADDR=0x82200000
+FW_PAYLOAD_ALIGN=0x1000
diff --git a/platform/fpga/openpiton/platform.c b/platform/fpga/openpiton/platform.c
index 7ca2123..5ff7d20 100644
--- a/platform/fpga/openpiton/platform.c
+++ b/platform/fpga/openpiton/platform.c
@@ -69,7 +69,7 @@ static struct aclint_mtimer_data mtimer = {
 static int openpiton_early_init(bool cold_boot)
 {
 	void *fdt;
-	struct platform_uart_data uart_data;
+	struct platform_uart_data uart_data = { 0 };
 	struct plic_data plic_data;
 	unsigned long aclint_freq;
 	uint64_t clint_addr;
diff --git a/platform/generic/Kconfig b/platform/generic/Kconfig
new file mode 100644
index 0000000..62c7a2d
--- /dev/null
+++ b/platform/generic/Kconfig
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config PLATFORM_GENERIC
+	bool
+	select FDT
+	select FDT_DOMAIN
+	select FDT_PMU
+	default y
+
+if PLATFORM_GENERIC
+
+config PLATFORM_GENERIC_NAME
+	string "Platform default name"
+	default "Generic"
+
+config PLATFORM_GENERIC_MAJOR_VER
+	int "Platform major version"
+	range 0 65535
+	default 0
+
+config PLATFORM_GENERIC_MINOR_VER
+	int "Platform minor version"
+	range 0 65535
+	default 1
+
+config PLATFORM_ALLWINNER_D1
+	bool "Allwinner D1 support"
+	depends on FDT_IRQCHIP_PLIC
+	default n
+
+config PLATFORM_ANDES_AE350
+	bool "Andes AE350 support"
+	default n
+
+config PLATFORM_RENESAS_RZFIVE
+	bool "Renesas RZ/Five support"
+	default n
+
+config PLATFORM_SIFIVE_FU540
+	bool "SiFive FU540 support"
+	default n
+
+config PLATFORM_SIFIVE_FU740
+	bool "SiFive FU740 support"
+	depends on FDT_RESET && FDT_I2C
+	default n
+
+endif
diff --git a/platform/generic/allwinner/objects.mk b/platform/generic/allwinner/objects.mk
index 9e36ab6..447cdf1 100644
--- a/platform/generic/allwinner/objects.mk
+++ b/platform/generic/allwinner/objects.mk
@@ -2,5 +2,5 @@
 # SPDX-License-Identifier: BSD-2-Clause
 #
 
-carray-platform_override_modules-y += sun20i_d1
-platform-objs-y += allwinner/sun20i-d1.o
+carray-platform_override_modules-$(CONFIG_PLATFORM_ALLWINNER_D1) += sun20i_d1
+platform-objs-$(CONFIG_PLATFORM_ALLWINNER_D1) += allwinner/sun20i-d1.o
diff --git a/platform/generic/allwinner/sun20i-d1.c b/platform/generic/allwinner/sun20i-d1.c
index 5b2656c..1da9e5b 100644
--- a/platform/generic/allwinner/sun20i-d1.c
+++ b/platform/generic/allwinner/sun20i-d1.c
@@ -5,11 +5,13 @@
  */
 
 #include <platform_override.h>
+#include <thead_c9xx.h>
 #include <sbi/riscv_io.h>
 #include <sbi/sbi_bitops.h>
 #include <sbi/sbi_ecall_interface.h>
 #include <sbi/sbi_error.h>
 #include <sbi/sbi_hsm.h>
+#include <sbi/sbi_pmu.h>
 #include <sbi_utils/fdt/fdt_helper.h>
 #include <sbi_utils/irqchip/fdt_irqchip_plic.h>
 
@@ -76,15 +78,16 @@ static u32 plic_threshold;
 
 static void sun20i_d1_plic_save(void)
 {
-	fdt_plic_context_save(true, plic_sie, &plic_threshold);
-	fdt_plic_priority_save(plic_priority);
+	fdt_plic_context_save(true, plic_sie, &plic_threshold, PLIC_IE_WORDS);
+	fdt_plic_priority_save(plic_priority, PLIC_SOURCES);
 }
 
 static void sun20i_d1_plic_restore(void)
 {
 	thead_plic_restore();
-	fdt_plic_priority_restore(plic_priority);
-	fdt_plic_context_restore(true, plic_sie, plic_threshold);
+	fdt_plic_priority_restore(plic_priority, PLIC_SOURCES);
+	fdt_plic_context_restore(true, plic_sie, plic_threshold,
+				 PLIC_IE_WORDS);
 }
 
 /*
@@ -144,14 +147,14 @@ static void sun20i_d1_riscv_cfg_restore(void)
 
 static void sun20i_d1_riscv_cfg_init(void)
 {
-	unsigned long entry = sbi_hartid_to_scratch(0)->warmboot_addr;
+	u64 entry = sbi_hartid_to_scratch(0)->warmboot_addr;
 
 	/* Enable MMIO access. */
 	writel_relaxed(CCU_BGR_ENABLE, SUN20I_D1_CCU_BASE + RISCV_CFG_BGR_REG);
 
 	/* Program the reset entry address. */
-	writel_relaxed((u32)entry, SUN20I_D1_RISCV_CFG_BASE + RESET_ENTRY_LO_REG);
-	writel_relaxed((u64)entry >> 32, SUN20I_D1_RISCV_CFG_BASE + RESET_ENTRY_HI_REG);
+	writel_relaxed(entry, SUN20I_D1_RISCV_CFG_BASE + RESET_ENTRY_LO_REG);
+	writel_relaxed(entry >> 32, SUN20I_D1_RISCV_CFG_BASE + RESET_ENTRY_HI_REG);
 }
 
 static int sun20i_d1_hart_suspend(u32 suspend_type)
@@ -199,6 +202,61 @@ static int sun20i_d1_final_init(bool cold_boot, const struct fdt_match *match)
 	return 0;
 }
 
+static void thead_c9xx_pmu_ctr_enable_irq(uint32_t ctr_idx)
+{
+	unsigned long mip_val;
+
+	if (ctr_idx >= SBI_PMU_HW_CTR_MAX)
+		return;
+
+	mip_val = csr_read(CSR_MIP);
+	/**
+	 * Clear out the OF bit so that next interrupt can be enabled.
+	 * This should be done only when the corresponding overflow interrupt
+	 * bit is cleared. That indicates that software has already handled the
+	 * previous interrupts or the hardware yet to set an overflow interrupt.
+	 * Otherwise, there will be race conditions where we may clear the bit
+	 * the software is yet to handle the interrupt.
+	 */
+	if (!(mip_val & THEAD_C9XX_MIP_MOIP))
+		csr_clear(THEAD_C9XX_CSR_MCOUNTEROF, BIT(ctr_idx));
+
+	/**
+	 * SSCOFPMF uses the OF bit for enabling/disabling the interrupt,
+	 * while the C9XX has designated enable bits.
+	 * So enable per-counter interrupt on C9xx here.
+	 */
+	csr_set(THEAD_C9XX_CSR_MCOUNTERINTEN, BIT(ctr_idx));
+}
+
+static void thead_c9xx_pmu_ctr_disable_irq(uint32_t ctr_idx)
+{
+	csr_clear(THEAD_C9XX_CSR_MCOUNTERINTEN, BIT(ctr_idx));
+}
+
+static int thead_c9xx_pmu_irq_bit(void)
+{
+	return THEAD_C9XX_MIP_MOIP;
+}
+
+const struct sbi_pmu_device thead_c9xx_pmu_device = {
+	.hw_counter_enable_irq = thead_c9xx_pmu_ctr_enable_irq,
+	.hw_counter_disable_irq = thead_c9xx_pmu_ctr_disable_irq,
+	.hw_counter_irq_bit = thead_c9xx_pmu_irq_bit,
+};
+
+static int sun20i_d1_extensions_init(const struct fdt_match *match,
+				     struct sbi_hart_features *hfeatures)
+{
+	sbi_pmu_set_device(&thead_c9xx_pmu_device);
+
+	/* auto-detection doesn't work on t-head c9xx cores */
+	hfeatures->mhpm_count = 29;
+	hfeatures->mhpm_bits = 64;
+
+	return 0;
+}
+
 static const struct fdt_match sun20i_d1_match[] = {
 	{ .compatible = "allwinner,sun20i-d1" },
 	{ },
@@ -207,4 +265,5 @@ static const struct fdt_match sun20i_d1_match[] = {
 const struct platform_override sun20i_d1 = {
 	.match_table	= sun20i_d1_match,
 	.final_init	= sun20i_d1_final_init,
+	.extensions_init = sun20i_d1_extensions_init,
 };
diff --git a/platform/generic/andes/ae350.c b/platform/generic/andes/ae350.c
new file mode 100644
index 0000000..cf7f6f2
--- /dev/null
+++ b/platform/generic/andes/ae350.c
@@ -0,0 +1,21 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Andes Technology Corporation
+ *
+ * Authors:
+ *   Yu Chien Peter Lin <peterlin@andestech.com>
+ */
+
+#include <platform_override.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+#include <sbi_utils/fdt/fdt_fixup.h>
+
+static const struct fdt_match andes_ae350_match[] = {
+	{ .compatible = "andestech,ae350" },
+	{ },
+};
+
+const struct platform_override andes_ae350 = {
+	.match_table = andes_ae350_match,
+};
diff --git a/platform/generic/andes/objects.mk b/platform/generic/andes/objects.mk
new file mode 100644
index 0000000..dd6408d
--- /dev/null
+++ b/platform/generic/andes/objects.mk
@@ -0,0 +1,6 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+carray-platform_override_modules-$(CONFIG_PLATFORM_ANDES_AE350) += andes_ae350
+platform-objs-$(CONFIG_PLATFORM_ANDES_AE350) += andes/ae350.o
diff --git a/platform/generic/config.mk b/platform/generic/config.mk
deleted file mode 100644
index 8151974..0000000
--- a/platform/generic/config.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2020 Western Digital Corporation or its affiliates.
-#
-# Authors:
-#   Anup Patel <anup.patel@wdc.com>
-#
-
-# Compiler flags
-platform-cppflags-y =
-platform-cflags-y =
-platform-asflags-y =
-platform-ldflags-y =
-
-# Command for platform specific "make run"
-platform-runcmd = qemu-system-riscv$(PLATFORM_RISCV_XLEN) -M virt -m 256M \
-  -nographic -bios $(build_dir)/platform/generic/firmware/fw_payload.elf
-
-# Blobs to build
-FW_TEXT_START=0x80000000
-FW_DYNAMIC=y
-FW_JUMP=y
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-  # This needs to be 4MB aligned for 32-bit system
-  FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x400000)))
-else
-  # This needs to be 2MB aligned for 64-bit system
-  FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x200000)))
-endif
-FW_JUMP_FDT_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x2200000)))
-FW_PAYLOAD=y
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-  # This needs to be 4MB aligned for 32-bit system
-  FW_PAYLOAD_OFFSET=0x400000
-else
-  # This needs to be 2MB aligned for 64-bit system
-  FW_PAYLOAD_OFFSET=0x200000
-endif
-FW_PAYLOAD_FDT_ADDR=$(FW_JUMP_FDT_ADDR)
diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
new file mode 100644
index 0000000..47fca95
--- /dev/null
+++ b/platform/generic/configs/defconfig
@@ -0,0 +1,37 @@
+CONFIG_PLATFORM_ALLWINNER_D1=y
+CONFIG_PLATFORM_ANDES_AE350=y
+CONFIG_PLATFORM_RENESAS_RZFIVE=y
+CONFIG_PLATFORM_SIFIVE_FU540=y
+CONFIG_PLATFORM_SIFIVE_FU740=y
+CONFIG_FDT_GPIO=y
+CONFIG_FDT_GPIO_SIFIVE=y
+CONFIG_FDT_I2C=y
+CONFIG_FDT_I2C_SIFIVE=y
+CONFIG_FDT_IPI=y
+CONFIG_FDT_IPI_MSWI=y
+CONFIG_FDT_IPI_PLICSW=y
+CONFIG_FDT_IRQCHIP=y
+CONFIG_FDT_IRQCHIP_APLIC=y
+CONFIG_FDT_IRQCHIP_IMSIC=y
+CONFIG_FDT_IRQCHIP_PLIC=y
+CONFIG_FDT_RESET=y
+CONFIG_FDT_RESET_ATCWDT200=y
+CONFIG_FDT_RESET_GPIO=y
+CONFIG_FDT_RESET_HTIF=y
+CONFIG_FDT_RESET_SIFIVE_TEST=y
+CONFIG_FDT_RESET_SUNXI_WDT=y
+CONFIG_FDT_RESET_THEAD=y
+CONFIG_FDT_SERIAL=y
+CONFIG_FDT_SERIAL_CADENCE=y
+CONFIG_FDT_SERIAL_GAISLER=y
+CONFIG_FDT_SERIAL_HTIF=y
+CONFIG_FDT_SERIAL_RENESAS_SCIF=y
+CONFIG_FDT_SERIAL_SHAKTI=y
+CONFIG_FDT_SERIAL_SIFIVE=y
+CONFIG_FDT_SERIAL_LITEX=y
+CONFIG_FDT_SERIAL_UART8250=y
+CONFIG_FDT_SERIAL_XILINX_UARTLITE=y
+CONFIG_FDT_TIMER=y
+CONFIG_FDT_TIMER_MTIMER=y
+CONFIG_FDT_TIMER_PLMT=y
+CONFIG_SERIAL_SEMIHOSTING=y
diff --git a/platform/generic/include/platform_override.h b/platform/generic/include/platform_override.h
index e55da25..7f1558d 100644
--- a/platform/generic/include/platform_override.h
+++ b/platform/generic/include/platform_override.h
@@ -10,6 +10,7 @@
 #ifndef __PLATFORM_OVERRIDE_H__
 #define __PLATFORM_OVERRIDE_H__
 
+#include <sbi/sbi_hart.h>
 #include <sbi/sbi_types.h>
 #include <sbi/sbi_trap.h>
 
@@ -22,6 +23,8 @@ struct platform_override {
 	void (*early_exit)(const struct fdt_match *match);
 	void (*final_exit)(const struct fdt_match *match);
 	int (*fdt_fixup)(void *fdt, const struct fdt_match *match);
+	int (*extensions_init)(const struct fdt_match *match,
+			       struct sbi_hart_features *hfeatures);
 	int (*vendor_ext_check)(long extid, const struct fdt_match *match);
 	int (*vendor_ext_provider)(long extid, long funcid,
 				   const struct sbi_trap_regs *regs,
diff --git a/platform/generic/include/thead_c9xx.h b/platform/generic/include/thead_c9xx.h
new file mode 100644
index 0000000..bab0408
--- /dev/null
+++ b/platform/generic/include/thead_c9xx.h
@@ -0,0 +1,127 @@
+#ifndef __RISCV_THEAD_C9XX_H____
+#define __RISCV_THEAD_C9XX_H____
+
+/* T-HEAD C9xx M mode CSR.  */
+#define THEAD_C9XX_CSR_MXSTATUS		0x7c0
+#define THEAD_C9XX_CSR_MHCR		0x7c1
+#define THEAD_C9XX_CSR_MCOR		0x7c2
+#define THEAD_C9XX_CSR_MCCR2		0x7c3
+#define THEAD_C9XX_CSR_MCER2		0x7c4
+#define THEAD_C9XX_CSR_MHINT		0x7c5
+#define THEAD_C9XX_CSR_MRMR		0x7c6
+#define THEAD_C9XX_CSR_MRVBR		0x7c7
+#define THEAD_C9XX_CSR_MCER		0x7c8
+#define THEAD_C9XX_CSR_MCOUNTERWEN	0x7c9
+#define THEAD_C9XX_CSR_MCOUNTERINTEN	0x7ca
+#define THEAD_C9XX_CSR_MCOUNTEROF	0x7cb
+#define THEAD_C9XX_CSR_MHINT2		0x7cc
+#define THEAD_C9XX_CSR_MHINT3		0x7cd
+#define THEAD_C9XX_CSR_MRADDR		0x7e0
+#define THEAD_C9XX_CSR_MEXSTATUS	0x7e1
+#define THEAD_C9XX_CSR_MNMICAUSE	0x7e2
+#define THEAD_C9XX_CSR_MNMIPC		0x7e3
+#define THEAD_C9XX_CSR_MHPMCR		0x7f0
+#define THEAD_C9XX_CSR_MHPMSR		0x7f1
+#define THEAD_C9XX_CSR_MHPMER		0x7f2
+#define THEAD_C9XX_CSR_MSMPR		0x7f3
+#define THEAD_C9XX_CSR_MTEECFG		0x7f4
+#define THEAD_C9XX_CSR_MZONEID		0x7f5
+#define THEAD_C9XX_CSR_ML2CPID		0x7f6
+#define THEAD_C9XX_CSR_ML2WP		0x7f7
+#define THEAD_C9XX_CSR_MDTCMCR		0x7f8
+#define THEAD_C9XX_CSR_USP		0x7d1
+#define THEAD_C9XX_CSR_MCINS		0x7d2
+#define THEAD_C9XX_CSR_MCINDEX		0x7d3
+#define THEAD_C9XX_CSR_MCDATA0		0x7d4
+#define THEAD_C9XX_CSR_MCDATA1		0x7d5
+#define THEAD_C9XX_CSR_MEICR		0x7d6
+#define THEAD_C9XX_CSR_MEICR2		0x7d7
+#define THEAD_C9XX_CSR_MBEADDR		0x7d8
+#define THEAD_C9XX_CSR_MCPUID		0xfc0
+#define THEAD_C9XX_CSR_MAPBADDR		0xfc1
+#define THEAD_C9XX_CSR_MWMSR		0xfc2
+#define THEAD_C9XX_CSR_MHALTCAUSE	0xfe0
+#define THEAD_C9XX_CSR_MDBGINFO		0xfe1
+#define THEAD_C9XX_CSR_MPCFIFO		0xfe2
+
+/* T-HEAD C9xx S mode CSR.  */
+#define THEAD_C9XX_CSR_SXSTATUS		0x5c0
+#define THEAD_C9XX_CSR_SHCR		0x5c1
+#define THEAD_C9XX_CSR_SCER2		0x5c2
+#define THEAD_C9XX_CSR_SCER		0x5c3
+#define THEAD_C9XX_CSR_SCOUNTERINTEN	0x5c4
+#define THEAD_C9XX_CSR_SCOUNTEROF	0x5c5
+#define THEAD_C9XX_CSR_SHINT		0x5c6
+#define THEAD_C9XX_CSR_SHINT2		0x5c7
+#define THEAD_C9XX_CSR_SHPMINHIBIT	0x5c8
+#define THEAD_C9XX_CSR_SHPMCR		0x5c9
+#define THEAD_C9XX_CSR_SHPMSR		0x5ca
+#define THEAD_C9XX_CSR_SHPMER		0x5cb
+#define THEAD_C9XX_CSR_SL2CPID		0x5cc
+#define THEAD_C9XX_CSR_SL2WP		0x5cd
+#define THEAD_C9XX_CSR_SBEADDR		0x5d0
+#define THEAD_C9XX_CSR_SCYCLE		0x5e0
+#define THEAD_C9XX_CSR_SHPMCOUNTER1	0x5e1
+#define THEAD_C9XX_CSR_SHPMCOUNTER2	0x5e2
+#define THEAD_C9XX_CSR_SHPMCOUNTER3	0x5e3
+#define THEAD_C9XX_CSR_SHPMCOUNTER4	0x5e4
+#define THEAD_C9XX_CSR_SHPMCOUNTER5	0x5e5
+#define THEAD_C9XX_CSR_SHPMCOUNTER6	0x5e6
+#define THEAD_C9XX_CSR_SHPMCOUNTER7	0x5e7
+#define THEAD_C9XX_CSR_SHPMCOUNTER8	0x5e8
+#define THEAD_C9XX_CSR_SHPMCOUNTER9	0x5e9
+#define THEAD_C9XX_CSR_SHPMCOUNTER10	0x5ea
+#define THEAD_C9XX_CSR_SHPMCOUNTER11	0x5eb
+#define THEAD_C9XX_CSR_SHPMCOUNTER12	0x5ec
+#define THEAD_C9XX_CSR_SHPMCOUNTER13	0x5ed
+#define THEAD_C9XX_CSR_SHPMCOUNTER14	0x5ee
+#define THEAD_C9XX_CSR_SHPMCOUNTER15	0x5ef
+#define THEAD_C9XX_CSR_SHPMCOUNTER16	0x5f0
+#define THEAD_C9XX_CSR_SHPMCOUNTER17	0x5f1
+#define THEAD_C9XX_CSR_SHPMCOUNTER18	0x5f2
+#define THEAD_C9XX_CSR_SHPMCOUNTER19	0x5f3
+#define THEAD_C9XX_CSR_SHPMCOUNTER20	0x5f4
+#define THEAD_C9XX_CSR_SHPMCOUNTER21	0x5f5
+#define THEAD_C9XX_CSR_SHPMCOUNTER22	0x5f6
+#define THEAD_C9XX_CSR_SHPMCOUNTER23	0x5f7
+#define THEAD_C9XX_CSR_SHPMCOUNTER24	0x5f8
+#define THEAD_C9XX_CSR_SHPMCOUNTER25	0x5f9
+#define THEAD_C9XX_CSR_SHPMCOUNTER26	0x5fa
+#define THEAD_C9XX_CSR_SHPMCOUNTER27	0x5fb
+#define THEAD_C9XX_CSR_SHPMCOUNTER28	0x5fc
+#define THEAD_C9XX_CSR_SHPMCOUNTER29	0x5fd
+#define THEAD_C9XX_CSR_SHPMCOUNTER30	0x5fe
+#define THEAD_C9XX_CSR_SHPMCOUNTER31	0x5ff
+
+/* T-HEAD C9xx U mode CSR.  */
+#define THEAD_C9XX_CSR_FXCR		0x800
+
+/* T-HEAD C9xx MMU extentions.  */
+#define THEAD_C9XX_CSR_SMIR		0x9c0
+#define THEAD_C9XX_CSR_SMEL		0x9c1
+#define THEAD_C9XX_CSR_SMEH		0x9c2
+#define THEAD_C9XX_CSR_SMCIR		0x9c3
+
+/* T-HEAD C9xx Security CSR(May be droped).  */
+#define THEAD_C9XX_CSR_MEBR		0xbe0
+#define THEAD_C9XX_CSR_NT_MSTATUS	0xbe1
+#define THEAD_C9XX_CSR_NT_MIE		0xbe2
+#define THEAD_C9XX_CSR_NT_MTVEC		0xbe3
+#define THEAD_C9XX_CSR_NT_MTVT		0xbe4
+#define THEAD_C9XX_CSR_NT_MEPC		0xbe5
+#define THEAD_C9XX_CSR_NT_MCAUSE	0xbe6
+#define THEAD_C9XX_CSR_NT_MIP		0xbe7
+#define THEAD_C9XX_CSR_NT_MINTSTATE	0xbe8
+#define THEAD_C9XX_CSR_NT_MXSTATUS	0xbe9
+#define THEAD_C9XX_CSR_NT_MEBR		0xbea
+#define THEAD_C9XX_CSR_NT_MSP		0xbeb
+#define THEAD_C9XX_CSR_T_USP		0xbec
+#define THEAD_C9XX_CSR_T_MDCR		0xbed
+#define THEAD_C9XX_CSR_T_MPCR		0xbee
+#define THEAD_C9XX_CSR_PMPTEECFG	0xbef
+
+/* T-HEAD C9xx MIP CSR extension */
+#define THEAD_C9XX_IRQ_PMU_OVF		17
+#define THEAD_C9XX_MIP_MOIP		(_UL(1) << THEAD_C9XX_IRQ_PMU_OVF)
+
+#endif
diff --git a/platform/generic/objects.mk b/platform/generic/objects.mk
index 4907754..136853e 100644
--- a/platform/generic/objects.mk
+++ b/platform/generic/objects.mk
@@ -7,5 +7,38 @@
 #   Anup Patel <anup.patel@wdc.com>
 #
 
+# Compiler flags
+platform-cppflags-y =
+platform-cflags-y =
+platform-asflags-y =
+platform-ldflags-y =
+
+# Command for platform specific "make run"
+platform-runcmd = qemu-system-riscv$(PLATFORM_RISCV_XLEN) -M virt -m 256M \
+  -nographic -bios $(build_dir)/platform/generic/firmware/fw_payload.elf
+
+# Objects to build
 platform-objs-y += platform.o
 platform-objs-y += platform_override_modules.o
+
+# Blobs to build
+FW_TEXT_START=0x80000000
+FW_DYNAMIC=y
+FW_JUMP=y
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+  # This needs to be 4MB aligned for 32-bit system
+  FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x400000)))
+else
+  # This needs to be 2MB aligned for 64-bit system
+  FW_JUMP_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x200000)))
+endif
+FW_JUMP_FDT_ADDR=$(shell printf "0x%X" $$(($(FW_TEXT_START) + 0x2200000)))
+FW_PAYLOAD=y
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+  # This needs to be 4MB aligned for 32-bit system
+  FW_PAYLOAD_OFFSET=0x400000
+else
+  # This needs to be 2MB aligned for 64-bit system
+  FW_PAYLOAD_OFFSET=0x200000
+endif
+FW_PAYLOAD_FDT_ADDR=$(FW_JUMP_FDT_ADDR)
diff --git a/platform/generic/platform.c b/platform/generic/platform.c
index bf93af6..bfe15f0 100644
--- a/platform/generic/platform.c
+++ b/platform/generic/platform.c
@@ -23,6 +23,7 @@
 #include <sbi_utils/timer/fdt_timer.h>
 #include <sbi_utils/ipi/fdt_ipi.h>
 #include <sbi_utils/reset/fdt_reset.h>
+#include <sbi_utils/serial/semihosting.h>
 
 /* List of platform override modules generated at compile time */
 extern const struct platform_override *platform_override_modules[];
@@ -33,17 +34,17 @@ static const struct fdt_match *generic_plat_match = NULL;
 
 static void fw_platform_lookup_special(void *fdt, int root_offset)
 {
-	int pos, noff;
 	const struct platform_override *plat;
 	const struct fdt_match *match;
+	int pos;
 
 	for (pos = 0; pos < platform_override_modules_size; pos++) {
 		plat = platform_override_modules[pos];
 		if (!plat->match_table)
 			continue;
 
-		noff = fdt_find_match(fdt, -1, plat->match_table, &match);
-		if (noff < 0)
+		match = fdt_match_node(fdt, root_offset, plat->match_table);
+		if (!match)
 			continue;
 
 		generic_plat = plat;
@@ -203,6 +204,15 @@ static void generic_final_exit(void)
 		generic_plat->final_exit(generic_plat_match);
 }
 
+static int generic_extensions_init(struct sbi_hart_features *hfeatures)
+{
+	if (generic_plat && generic_plat->extensions_init)
+		return generic_plat->extensions_init(generic_plat_match,
+						     hfeatures);
+
+	return 0;
+}
+
 static int generic_domains_init(void)
 {
 	return fdt_domains_populate(fdt_get_address());
@@ -242,14 +252,23 @@ static uint64_t generic_pmu_xlate_to_mhpmevent(uint32_t event_idx,
 	return evt_val;
 }
 
+static int generic_console_init(void)
+{
+	if (semihosting_enabled())
+		return semihosting_init();
+	else
+		return fdt_serial_init();
+}
+
 const struct sbi_platform_operations platform_ops = {
 	.nascent_init		= generic_nascent_init,
 	.early_init		= generic_early_init,
 	.final_init		= generic_final_init,
 	.early_exit		= generic_early_exit,
 	.final_exit		= generic_final_exit,
+	.extensions_init	= generic_extensions_init,
 	.domains_init		= generic_domains_init,
-	.console_init		= fdt_serial_init,
+	.console_init		= generic_console_init,
 	.irqchip_init		= fdt_irqchip_init,
 	.irqchip_exit		= fdt_irqchip_exit,
 	.ipi_init		= fdt_ipi_init,
@@ -265,8 +284,10 @@ const struct sbi_platform_operations platform_ops = {
 
 struct sbi_platform platform = {
 	.opensbi_version	= OPENSBI_VERSION,
-	.platform_version	= SBI_PLATFORM_VERSION(0x0, 0x01),
-	.name			= "Generic",
+	.platform_version	=
+		SBI_PLATFORM_VERSION(CONFIG_PLATFORM_GENERIC_MAJOR_VER,
+				     CONFIG_PLATFORM_GENERIC_MINOR_VER),
+	.name			= CONFIG_PLATFORM_GENERIC_NAME,
 	.features		= SBI_PLATFORM_DEFAULT_FEATURES,
 	.hart_count		= SBI_HARTMASK_MAX_BITS,
 	.hart_index2id		= generic_hart_index2id,
diff --git a/platform/generic/renesas/rzfive/objects.mk b/platform/generic/renesas/rzfive/objects.mk
new file mode 100644
index 0000000..2e7e37f
--- /dev/null
+++ b/platform/generic/renesas/rzfive/objects.mk
@@ -0,0 +1,8 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2022 Renesas Electronics Corp.
+#
+
+carray-platform_override_modules-$(CONFIG_PLATFORM_RENESAS_RZFIVE) += renesas_rzfive
+platform-objs-$(CONFIG_PLATFORM_RENESAS_RZFIVE) += renesas/rzfive/rzfive.o
diff --git a/platform/generic/renesas/rzfive/rzfive.c b/platform/generic/renesas/rzfive/rzfive.c
new file mode 100644
index 0000000..ca182e3
--- /dev/null
+++ b/platform/generic/renesas/rzfive/rzfive.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Renesas Electronics Corp.
+ *
+ */
+
+#include <platform_override.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+
+static const struct fdt_match renesas_rzfive_match[] = {
+	{ .compatible = "renesas,r9a07g043f01" },
+	{ /* sentinel */ }
+};
+
+const struct platform_override renesas_rzfive = {
+	.match_table = renesas_rzfive_match,
+};
diff --git a/platform/generic/sifive/objects.mk b/platform/generic/sifive/objects.mk
index c17e2df..d75e444 100644
--- a/platform/generic/sifive/objects.mk
+++ b/platform/generic/sifive/objects.mk
@@ -2,8 +2,8 @@
 # SPDX-License-Identifier: BSD-2-Clause
 #
 
-carray-platform_override_modules-y += sifive_fu540
-platform-objs-y += sifive/fu540.o
+carray-platform_override_modules-$(CONFIG_PLATFORM_SIFIVE_FU540) += sifive_fu540
+platform-objs-$(CONFIG_PLATFORM_SIFIVE_FU540) += sifive/fu540.o
 
-carray-platform_override_modules-y += sifive_fu740
-platform-objs-y += sifive/fu740.o
+carray-platform_override_modules-$(CONFIG_PLATFORM_SIFIVE_FU740) += sifive_fu740
+platform-objs-$(CONFIG_PLATFORM_SIFIVE_FU740) += sifive/fu740.o
diff --git a/platform/kendryte/k210/Kconfig b/platform/kendryte/k210/Kconfig
new file mode 100644
index 0000000..5bf5973
--- /dev/null
+++ b/platform/kendryte/k210/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config PLATFORM_KENDRYTE_K210
+	bool
+	select FDT
+	select IPI_MSWI
+	select IRQCHIP_PLIC
+	select SERIAL_SIFIVE
+	select TIMER_MTIMER
+	default y
diff --git a/platform/kendryte/k210/config.mk b/platform/kendryte/k210/config.mk
deleted file mode 100644
index 8a9b814..0000000
--- a/platform/kendryte/k210/config.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2019 Western Digital Corporation or its affiliates.
-#
-# Authors:
-#   Damien Le Moal <damien.lemoal@wdc.com>
-#
-
-# Compiler flags
-platform-cppflags-y =
-platform-cflags-y =
-platform-asflags-y =
-platform-ldflags-y =
-
-# Blobs to build
-FW_TEXT_START=0x80000000
-FW_PAYLOAD=y
-FW_PAYLOAD_ALIGN=0x1000
diff --git a/platform/kendryte/k210/configs/defconfig b/platform/kendryte/k210/configs/defconfig
new file mode 100644
index 0000000..e69de29
diff --git a/platform/kendryte/k210/objects.mk b/platform/kendryte/k210/objects.mk
index b74da74..1bfb898 100644
--- a/platform/kendryte/k210/objects.mk
+++ b/platform/kendryte/k210/objects.mk
@@ -7,8 +7,20 @@
 #   Damien Le Moal <damien.lemoal@wdc.com>
 #
 
+# Compiler flags
+platform-cppflags-y =
+platform-cflags-y =
+platform-asflags-y =
+platform-ldflags-y =
+
+# Objects to build
 platform-objs-y += platform.o
 
 platform-objs-y += k210.o
 platform-varprefix-k210.o = dt_k210
 platform-padding-k210.o = 2048
+
+# Blobs to build
+FW_TEXT_START=0x80000000
+FW_PAYLOAD=y
+FW_PAYLOAD_ALIGN=0x1000
diff --git a/platform/nuclei/ux600/Kconfig b/platform/nuclei/ux600/Kconfig
new file mode 100644
index 0000000..84d74e8
--- /dev/null
+++ b/platform/nuclei/ux600/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+config PLATFORM_NUCLEI_UX600
+	bool
+	select FDT
+	select IPI_MSWI
+	select IRQCHIP_PLIC
+	select SERIAL_SIFIVE
+	select TIMER_MTIMER
+	default y
diff --git a/platform/nuclei/ux600/config.mk b/platform/nuclei/ux600/config.mk
deleted file mode 100644
index dddcc4e..0000000
--- a/platform/nuclei/ux600/config.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2020 Nuclei Corporation or its affiliates.
-#
-# Authors:
-#   lujun <lujun@nucleisys.com>
-#   hqfang <578567190@qq.com>
-#
-
-# Compiler flags
-platform-cppflags-y =
-platform-cflags-y =
-platform-asflags-y =
-platform-ldflags-y =
-
-# Command for platform specific "make run"
-platform-runcmd = xl_spike \
-  $(build_dir)/platform/nuclei/ux600/firmware/fw_payload.elf
-
-# Blobs to build
-FW_TEXT_START=0xA0000000
-FW_DYNAMIC=y
-FW_JUMP=y
-
-FW_JUMP_ADDR=0xA0200000
-FW_JUMP_FDT_ADDR=0xA8000000
-FW_PAYLOAD=y
-FW_PAYLOAD_OFFSET=0x200000
-FW_PAYLOAD_FDT_ADDR=0xA8000000
diff --git a/platform/nuclei/ux600/configs/defconfig b/platform/nuclei/ux600/configs/defconfig
new file mode 100644
index 0000000..e69de29
diff --git a/platform/nuclei/ux600/objects.mk b/platform/nuclei/ux600/objects.mk
index 8c36c90..7c429e0 100644
--- a/platform/nuclei/ux600/objects.mk
+++ b/platform/nuclei/ux600/objects.mk
@@ -8,4 +8,26 @@
 #   hqfang <578567190@qq.com>
 #
 
+# Compiler flags
+platform-cppflags-y =
+platform-cflags-y =
+platform-asflags-y =
+platform-ldflags-y =
+
+# Command for platform specific "make run"
+platform-runcmd = xl_spike \
+  $(build_dir)/platform/nuclei/ux600/firmware/fw_payload.elf
+
+# Objects to build
 platform-objs-y += platform.o
+
+# Blobs to build
+FW_TEXT_START=0xA0000000
+FW_DYNAMIC=y
+FW_JUMP=y
+
+FW_JUMP_ADDR=0xA0200000
+FW_JUMP_FDT_ADDR=0xA8000000
+FW_PAYLOAD=y
+FW_PAYLOAD_OFFSET=0x200000
+FW_PAYLOAD_FDT_ADDR=0xA8000000
diff --git a/platform/nuclei/ux600/platform.c b/platform/nuclei/ux600/platform.c
index 5498a1f..2b02734 100644
--- a/platform/nuclei/ux600/platform.c
+++ b/platform/nuclei/ux600/platform.c
@@ -8,7 +8,6 @@
  *   hqfang <578567190@qq.com>
  */
 
-#include <libfdt.h>
 #include <sbi/riscv_asm.h>
 #include <sbi/riscv_io.h>
 #include <sbi/riscv_encoding.h>
diff --git a/platform/template/Kconfig b/platform/template/Kconfig
new file mode 100644
index 0000000..d73825d
--- /dev/null
+++ b/platform/template/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+#
+# All mandatory drivers or libraries for this platform should
+# be directly selected by the PLATFORM_xyz kconfig symbol.
+#
+# All optional drivers or libraries for this platform should
+# be enabled via configs/defconfig of this platform.
+#
+config PLATFORM_TEMPLATE
+	bool
+	select IPI_MSWI
+	select IRQCHIP_PLIC
+	select SERIAL_UART8250
+	select TIMER_MTIMER
+	default y
diff --git a/platform/template/config.mk b/platform/template/config.mk
deleted file mode 100644
index f817342..0000000
--- a/platform/template/config.mk
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2019 Western Digital Corporation or its affiliates.
-#
-
-# Compiler pre-processor flags
-platform-cppflags-y =
-
-# C Compiler and assembler flags.
-platform-cflags-y =
-platform-asflags-y =
-
-# Linker flags: additional libraries and object files that the platform
-# code needs can be added here
-platform-ldflags-y =
-
-#
-# Command for platform specific "make run"
-# Useful for development and debugging on plaftform simulator (such as QEMU)
-#
-# platform-runcmd = your_platform_run.sh
-
-#
-# Platform RISC-V XLEN, ABI, ISA and Code Model configuration.
-# These are optional parameters but platforms can optionaly provide it.
-# Some of these are guessed based on GCC compiler capabilities
-#
-# PLATFORM_RISCV_XLEN = 64
-# PLATFORM_RISCV_ABI = lp64
-# PLATFORM_RISCV_ISA = rv64imafdc
-# PLATFORM_RISCV_CODE_MODEL = medany
-
-# Firmware load address configuration. This is mandatory.
-FW_TEXT_START=0x80000000
-
-# Optional parameter for path to external FDT
-# FW_FDT_PATH="path to platform flattened device tree file"
-
-#
-# Dynamic firmware configuration.
-# Optional parameters are commented out. Uncomment and define these parameters
-# as needed.
-#
-FW_DYNAMIC=<y|n>
-
-#
-# Jump firmware configuration.
-# Optional parameters are commented out. Uncomment and define these parameters
-# as needed.
-#
-FW_JUMP=<y|n>
-# This needs to be 4MB aligned for 32-bit support
-# This needs to be 2MB aligned for 64-bit support
-# ifeq ($(PLATFORM_RISCV_XLEN), 32)
-# FW_JUMP_ADDR=0x80400000
-# else
-# FW_JUMP_ADDR=0x80200000
-# endif
-# FW_JUMP_FDT_ADDR=0x82200000
-
-#
-# Firmware with payload configuration.
-# Optional parameters are commented out. Uncomment and define these parameters
-# as needed.
-#
-FW_PAYLOAD=<y|n>
-# This needs to be 4MB aligned for 32-bit support
-# This needs to be 2MB aligned for 64-bit support
-ifeq ($(PLATFORM_RISCV_XLEN), 32)
-FW_PAYLOAD_OFFSET=0x400000
-else
-FW_PAYLOAD_OFFSET=0x200000
-endif
-# FW_PAYLOAD_ALIGN=0x1000
-# FW_PAYLOAD_PATH="path to next boot stage binary image file"
-# FW_PAYLOAD_FDT_ADDR=0x82200000
diff --git a/platform/template/configs/defconfig b/platform/template/configs/defconfig
new file mode 100644
index 0000000..e69de29
diff --git a/platform/template/objects.mk b/platform/template/objects.mk
index caaec2b..0025928 100644
--- a/platform/template/objects.mk
+++ b/platform/template/objects.mk
@@ -4,6 +4,33 @@
 # Copyright (c) 2019 Western Digital Corporation or its affiliates.
 #
 
+# Compiler pre-processor flags
+platform-cppflags-y =
+
+# C Compiler and assembler flags.
+platform-cflags-y =
+platform-asflags-y =
+
+# Linker flags: additional libraries and object files that the platform
+# code needs can be added here
+platform-ldflags-y =
+
+#
+# Command for platform specific "make run"
+# Useful for development and debugging on plaftform simulator (such as QEMU)
+#
+# platform-runcmd = your_platform_run.sh
+
+#
+# Platform RISC-V XLEN, ABI, ISA and Code Model configuration.
+# These are optional parameters but platforms can optionaly provide it.
+# Some of these are guessed based on GCC compiler capabilities
+#
+# PLATFORM_RISCV_XLEN = 64
+# PLATFORM_RISCV_ABI = lp64
+# PLATFORM_RISCV_ISA = rv64imafdc
+# PLATFORM_RISCV_CODE_MODEL = medany
+
 # Space separated list of object file names to be compiled for the platform
 platform-objs-y += platform.o
 
@@ -13,3 +40,48 @@ platform-objs-y += platform.o
 # source file be in the form <dt file name>.dts
 #
 # platform-objs-y += <dt file name>.o
+
+# Firmware load address configuration. This is mandatory.
+FW_TEXT_START=0x80000000
+
+# Optional parameter for path to external FDT
+# FW_FDT_PATH="path to platform flattened device tree file"
+
+#
+# Dynamic firmware configuration.
+# Optional parameters are commented out. Uncomment and define these parameters
+# as needed.
+#
+FW_DYNAMIC=<y|n>
+
+#
+# Jump firmware configuration.
+# Optional parameters are commented out. Uncomment and define these parameters
+# as needed.
+#
+FW_JUMP=<y|n>
+# This needs to be 4MB aligned for 32-bit support
+# This needs to be 2MB aligned for 64-bit support
+# ifeq ($(PLATFORM_RISCV_XLEN), 32)
+# FW_JUMP_ADDR=0x80400000
+# else
+# FW_JUMP_ADDR=0x80200000
+# endif
+# FW_JUMP_FDT_ADDR=0x82200000
+
+#
+# Firmware with payload configuration.
+# Optional parameters are commented out. Uncomment and define these parameters
+# as needed.
+#
+FW_PAYLOAD=<y|n>
+# This needs to be 4MB aligned for 32-bit support
+# This needs to be 2MB aligned for 64-bit support
+ifeq ($(PLATFORM_RISCV_XLEN), 32)
+FW_PAYLOAD_OFFSET=0x400000
+else
+FW_PAYLOAD_OFFSET=0x200000
+endif
+# FW_PAYLOAD_ALIGN=0x1000
+# FW_PAYLOAD_PATH="path to next boot stage binary image file"
+# FW_PAYLOAD_FDT_ADDR=0x82200000
diff --git a/platform/template/platform.c b/platform/template/platform.c
index 5524847..f3802da 100644
--- a/platform/template/platform.c
+++ b/platform/template/platform.c
@@ -11,7 +11,7 @@
 
 /*
  * Include these files as needed.
- * See config.mk PLATFORM_xxx configuration parameters.
+ * See objects.mk PLATFORM_xxx configuration parameters.
  */
 #include <sbi_utils/ipi/aclint_mswi.h>
 #include <sbi_utils/irqchip/plic.h>
diff --git a/scripts/Kconfiglib/LICENSE.txt b/scripts/Kconfiglib/LICENSE.txt
new file mode 100644
index 0000000..8b31efc
--- /dev/null
+++ b/scripts/Kconfiglib/LICENSE.txt
@@ -0,0 +1,5 @@
+Copyright (c) 2011-2019, Ulf Magnusson <ulfalizer@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/scripts/Kconfiglib/allnoconfig.py b/scripts/Kconfiglib/allnoconfig.py
new file mode 100755
index 0000000..de90d8b
--- /dev/null
+++ b/scripts/Kconfiglib/allnoconfig.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018-2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Writes a configuration file where as many symbols as possible are set to 'n'.
+
+The default output filename is '.config'. A different filename can be passed
+in the KCONFIG_CONFIG environment variable.
+
+Usage for the Linux kernel:
+
+  $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/allnoconfig.py
+"""
+
+# See examples/allnoconfig_walk.py for another way to implement this script
+
+import kconfiglib
+
+
+def main():
+    kconf = kconfiglib.standard_kconfig(__doc__)
+
+    # Avoid warnings that would otherwise get printed by Kconfiglib for the
+    # following:
+    #
+    # 1. Assigning a value to a symbol without a prompt, which never has any
+    #    effect
+    #
+    # 2. Assigning values invalid for the type (only bool/tristate symbols
+    #    accept 0/1/2, for n/m/y). The assignments will be ignored for other
+    #    symbol types, which is what we want.
+    kconf.warn = False
+    for sym in kconf.unique_defined_syms:
+        sym.set_value(2 if sym.is_allnoconfig_y else 0)
+    kconf.warn = True
+
+    kconf.load_allconfig("allno.config")
+
+    print(kconf.write_config())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/Kconfiglib/allyesconfig.py b/scripts/Kconfiglib/allyesconfig.py
new file mode 100755
index 0000000..90eb9b8
--- /dev/null
+++ b/scripts/Kconfiglib/allyesconfig.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018-2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Writes a configuration file where as many symbols as possible are set to 'y'.
+
+The default output filename is '.config'. A different filename can be passed
+in the KCONFIG_CONFIG environment variable.
+
+Usage for the Linux kernel:
+
+  $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/allyesconfig.py
+"""
+import kconfiglib
+
+
+def main():
+    kconf = kconfiglib.standard_kconfig(__doc__)
+
+    # See allnoconfig.py
+    kconf.warn = False
+
+    # Try to set all symbols to 'y'. Dependencies might truncate the value down
+    # later, but this will at least give the highest possible value.
+    #
+    # Assigning 0/1/2 to non-bool/tristate symbols has no effect (int/hex
+    # symbols still take a string, because they preserve formatting).
+    for sym in kconf.unique_defined_syms:
+        # Set choice symbols to 'm'. This value will be ignored for choices in
+        # 'y' mode (the "normal" mode), which will instead just get their
+        # default selection, but will set all symbols in m-mode choices to 'm',
+        # which is as high as they can go.
+        #
+        # Here's a convoluted example of how you might get an m-mode choice
+        # even during allyesconfig:
+        #
+        #   choice
+        #           tristate "weird choice"
+        #           depends on m
+        sym.set_value(1 if sym.choice else 2)
+
+    # Set all choices to the highest possible mode
+    for choice in kconf.unique_choices:
+        choice.set_value(2)
+
+    kconf.warn = True
+
+    kconf.load_allconfig("allyes.config")
+
+    print(kconf.write_config())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/Kconfiglib/defconfig.py b/scripts/Kconfiglib/defconfig.py
new file mode 100755
index 0000000..b179273
--- /dev/null
+++ b/scripts/Kconfiglib/defconfig.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Reads a specified configuration file, then writes a new configuration file.
+This can be used to initialize the configuration from e.g. an arch-specific
+configuration file. This input configuration file would usually be a minimal
+configuration file, as generated by e.g. savedefconfig.
+
+The default output filename is '.config'. A different filename can be passed in
+the KCONFIG_CONFIG environment variable.
+"""
+import argparse
+
+import kconfiglib
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=__doc__)
+
+    parser.add_argument(
+        "--kconfig",
+        default="Kconfig",
+        help="Top-level Kconfig file (default: Kconfig)")
+
+    parser.add_argument(
+        "config",
+        metavar="CONFIGURATION",
+        help="Input configuration file")
+
+    args = parser.parse_args()
+
+    kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True)
+    print(kconf.load_config(args.config))
+    print(kconf.write_config())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/Kconfiglib/genconfig.py b/scripts/Kconfiglib/genconfig.py
new file mode 100755
index 0000000..62f065b
--- /dev/null
+++ b/scripts/Kconfiglib/genconfig.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018-2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Generates a header file with #defines from the configuration, matching the
+format of include/generated/autoconf.h in the Linux kernel.
+
+Optionally, also writes the configuration output as a .config file. See
+--config-out.
+
+The --sync-deps, --file-list, and --env-list options generate information that
+can be used to avoid needless rebuilds/reconfigurations.
+
+Before writing a header or configuration file, Kconfiglib compares the old
+contents of the file against the new contents. If there's no change, the write
+is skipped. This avoids updating file metadata like the modification time, and
+might save work depending on your build setup.
+
+By default, the configuration is generated from '.config'. A different
+configuration file can be passed in the KCONFIG_CONFIG environment variable.
+
+A custom header string can be inserted at the beginning of generated
+configuration and header files by setting the KCONFIG_CONFIG_HEADER and
+KCONFIG_AUTOHEADER_HEADER environment variables, respectively (this also works
+for other scripts). The string is not automatically made a comment (this is by
+design, to allow anything to be added), and no trailing newline is added, so
+add '/* */', '#', and newlines as appropriate.
+
+See https://www.gnu.org/software/make/manual/make.html#Multi_002dLine for a
+handy way to define multi-line variables in makefiles, for use with custom
+headers. Remember to export the variable to the environment.
+"""
+import argparse
+import os
+import sys
+
+import kconfiglib
+
+
+DEFAULT_SYNC_DEPS_PATH = "deps/"
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=__doc__)
+
+    parser.add_argument(
+        "--header-path",
+        metavar="HEADER_FILE",
+        help="""
+Path to write the generated header file to. If not specified, the path in the
+environment variable KCONFIG_AUTOHEADER is used if it is set, and 'config.h'
+otherwise.
+""")
+
+    parser.add_argument(
+        "--config-out",
+        metavar="CONFIG_FILE",
+        help="""
+Write the configuration to CONFIG_FILE. This is useful if you include .config
+files in Makefiles, as the generated configuration file will be a full .config
+file even if .config is outdated. The generated configuration matches what
+olddefconfig would produce. If you use sync-deps, you can include
+deps/auto.conf instead. --config-out is meant for cases where incremental build
+information isn't needed.
+""")
+
+    parser.add_argument(
+        "--sync-deps",
+        metavar="OUTPUT_DIR",
+        nargs="?",
+        const=DEFAULT_SYNC_DEPS_PATH,
+        help="""
+Enable generation of symbol dependency information for incremental builds,
+optionally specifying the output directory (default: {}). See the docstring of
+Kconfig.sync_deps() in Kconfiglib for more information.
+""".format(DEFAULT_SYNC_DEPS_PATH))
+
+    parser.add_argument(
+        "--file-list",
+        metavar="OUTPUT_FILE",
+        help="""
+Write a list of all Kconfig files to OUTPUT_FILE, with one file per line. The
+paths are relative to $srctree (or to the current directory if $srctree is
+unset). Files appear in the order they're 'source'd.
+""")
+
+    parser.add_argument(
+        "--env-list",
+        metavar="OUTPUT_FILE",
+        help="""
+Write a list of all environment variables referenced in Kconfig files to
+OUTPUT_FILE, with one variable per line. Each line has the format NAME=VALUE.
+Only environment variables referenced with the preprocessor $(VAR) syntax are
+included, and not variables referenced with the older $VAR syntax (which is
+only supported for backwards compatibility).
+""")
+
+    parser.add_argument(
+        "kconfig",
+        metavar="KCONFIG",
+        nargs="?",
+        default="Kconfig",
+        help="Top-level Kconfig file (default: Kconfig)")
+
+    args = parser.parse_args()
+
+
+    kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True)
+    kconf.load_config()
+
+    if args.header_path is None:
+        if "KCONFIG_AUTOHEADER" in os.environ:
+            kconf.write_autoconf()
+        else:
+            # Kconfiglib defaults to include/generated/autoconf.h to be
+            # compatible with the C tools. 'config.h' is used here instead for
+            # backwards compatibility. It's probably a saner default for tools
+            # as well.
+            kconf.write_autoconf("config.h")
+    else:
+        kconf.write_autoconf(args.header_path)
+
+    if args.config_out is not None:
+        kconf.write_config(args.config_out, save_old=False)
+
+    if args.sync_deps is not None:
+        kconf.sync_deps(args.sync_deps)
+
+    if args.file_list is not None:
+        with _open_write(args.file_list) as f:
+            for path in kconf.kconfig_filenames:
+                f.write(path + "\n")
+
+    if args.env_list is not None:
+        with _open_write(args.env_list) as f:
+            for env_var in kconf.env_vars:
+                f.write("{}={}\n".format(env_var, os.environ[env_var]))
+
+
+def _open_write(path):
+    # Python 2/3 compatibility. io.open() is available on both, but makes
+    # write() expect 'unicode' strings on Python 2.
+
+    if sys.version_info[0] < 3:
+        return open(path, "w")
+    return open(path, "w", encoding="utf-8")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/Kconfiglib/kconfiglib.py b/scripts/Kconfiglib/kconfiglib.py
new file mode 100644
index 0000000..c67895c
--- /dev/null
+++ b/scripts/Kconfiglib/kconfiglib.py
@@ -0,0 +1,7160 @@
+# Copyright (c) 2011-2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Overview
+========
+
+Kconfiglib is a Python 2/3 library for scripting and extracting information
+from Kconfig (https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt)
+configuration systems.
+
+See the homepage at https://github.com/ulfalizer/Kconfiglib for a longer
+overview.
+
+Since Kconfiglib 12.0.0, the library version is available in
+kconfiglib.VERSION, which is a (<major>, <minor>, <patch>) tuple, e.g.
+(12, 0, 0).
+
+
+Using Kconfiglib on the Linux kernel with the Makefile targets
+==============================================================
+
+For the Linux kernel, a handy interface is provided by the
+scripts/kconfig/Makefile patch, which can be applied with either 'git am' or
+the 'patch' utility:
+
+  $ wget -qO- https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch | git am
+  $ wget -qO- https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch | patch -p1
+
+Warning: Not passing -p1 to patch will cause the wrong file to be patched.
+
+Please tell me if the patch does not apply. It should be trivial to apply
+manually, as it's just a block of text that needs to be inserted near the other
+*conf: targets in scripts/kconfig/Makefile.
+
+Look further down for a motivation for the Makefile patch and for instructions
+on how you can use Kconfiglib without it.
+
+If you do not wish to install Kconfiglib via pip, the Makefile patch is set up
+so that you can also just clone Kconfiglib into the kernel root:
+
+  $ git clone git://github.com/ulfalizer/Kconfiglib.git
+  $ git am Kconfiglib/makefile.patch  (or 'patch -p1 < Kconfiglib/makefile.patch')
+
+Warning: The directory name Kconfiglib/ is significant in this case, because
+it's added to PYTHONPATH by the new targets in makefile.patch.
+
+The targets added by the Makefile patch are described in the following
+sections.
+
+
+make kmenuconfig
+----------------
+
+This target runs the curses menuconfig interface with Python 3. As of
+Kconfiglib 12.2.0, both Python 2 and Python 3 are supported (previously, only
+Python 3 was supported, so this was a backport).
+
+
+make guiconfig
+--------------
+
+This target runs the Tkinter menuconfig interface. Both Python 2 and Python 3
+are supported. To change the Python interpreter used, pass
+PYTHONCMD=<executable> to 'make'. The default is 'python'.
+
+
+make [ARCH=<arch>] iscriptconfig
+--------------------------------
+
+This target gives an interactive Python prompt where a Kconfig instance has
+been preloaded and is available in 'kconf'. To change the Python interpreter
+used, pass PYTHONCMD=<executable> to 'make'. The default is 'python'.
+
+To get a feel for the API, try evaluating and printing the symbols in
+kconf.defined_syms, and explore the MenuNode menu tree starting at
+kconf.top_node by following 'next' and 'list' pointers.
+
+The item contained in a menu node is found in MenuNode.item (note that this can
+be one of the constants kconfiglib.MENU and kconfiglib.COMMENT), and all
+symbols and choices have a 'nodes' attribute containing their menu nodes
+(usually only one). Printing a menu node will print its item, in Kconfig
+format.
+
+If you want to look up a symbol by name, use the kconf.syms dictionary.
+
+
+make scriptconfig SCRIPT=<script> [SCRIPT_ARG=<arg>]
+----------------------------------------------------
+
+This target runs the Python script given by the SCRIPT parameter on the
+configuration. sys.argv[1] holds the name of the top-level Kconfig file
+(currently always "Kconfig" in practice), and sys.argv[2] holds the SCRIPT_ARG
+argument, if given.
+
+See the examples/ subdirectory for example scripts.
+
+
+make dumpvarsconfig
+-------------------
+
+This target prints a list of all environment variables referenced from the
+Kconfig files, together with their values. See the
+Kconfiglib/examples/dumpvars.py script.
+
+Only environment variables that are referenced via the Kconfig preprocessor
+$(FOO) syntax are included. The preprocessor was added in Linux 4.18.
+
+
+Using Kconfiglib without the Makefile targets
+=============================================
+
+The make targets are only needed to pick up environment variables exported from
+the Kbuild makefiles and referenced inside Kconfig files, via e.g.
+'source "arch/$(SRCARCH)/Kconfig" and commands run via '$(shell,...)'.
+
+These variables are referenced as of writing (Linux 4.18), together with sample
+values:
+
+  srctree          (.)
+  ARCH             (x86)
+  SRCARCH          (x86)
+  KERNELVERSION    (4.18.0)
+  CC               (gcc)
+  HOSTCC           (gcc)
+  HOSTCXX          (g++)
+  CC_VERSION_TEXT  (gcc (Ubuntu 7.3.0-16ubuntu3) 7.3.0)
+
+Older kernels only reference ARCH, SRCARCH, and KERNELVERSION.
+
+If your kernel is recent enough (4.18+), you can get a list of referenced
+environment variables via 'make dumpvarsconfig' (see above). Note that this
+command is added by the Makefile patch.
+
+To run Kconfiglib without the Makefile patch, set the environment variables
+manually:
+
+  $ srctree=. ARCH=x86 SRCARCH=x86 KERNELVERSION=`make kernelversion` ... python(3)
+  >>> import kconfiglib
+  >>> kconf = kconfiglib.Kconfig()  # filename defaults to "Kconfig"
+
+Search the top-level Makefile for "Additional ARCH settings" to see other
+possibilities for ARCH and SRCARCH.
+
+
+Intro to symbol values
+======================
+
+Kconfiglib has the same assignment semantics as the C implementation.
+
+Any symbol can be assigned a value by the user (via Kconfig.load_config() or
+Symbol.set_value()), but this user value is only respected if the symbol is
+visible, which corresponds to it (currently) being visible in the menuconfig
+interface.
+
+For symbols with prompts, the visibility of the symbol is determined by the
+condition on the prompt. Symbols without prompts are never visible, so setting
+a user value on them is pointless. A warning will be printed by default if
+Symbol.set_value() is called on a promptless symbol. Assignments to promptless
+symbols are normal within a .config file, so no similar warning will be printed
+by load_config().
+
+Dependencies from parents and 'if'/'depends on' are propagated to properties,
+including prompts, so these two configurations are logically equivalent:
+
+(1)
+
+  menu "menu"
+      depends on A
+
+  if B
+
+  config FOO
+      tristate "foo" if D
+      default y
+      depends on C
+
+  endif
+
+  endmenu
+
+(2)
+
+  menu "menu"
+      depends on A
+
+  config FOO
+      tristate "foo" if A && B && C && D
+      default y if A && B && C
+
+  endmenu
+
+In this example, A && B && C && D (the prompt condition) needs to be non-n for
+FOO to be visible (assignable). If its value is m, the symbol can only be
+assigned the value m: The visibility sets an upper bound on the value that can
+be assigned by the user, and any higher user value will be truncated down.
+
+'default' properties are independent of the visibility, though a 'default' will
+often get the same condition as the prompt due to dependency propagation.
+'default' properties are used if the symbol is not visible or has no user
+value.
+
+Symbols with no user value (or that have a user value but are not visible) and
+no (active) 'default' default to n for bool/tristate symbols, and to the empty
+string for other symbol types.
+
+'select' works similarly to symbol visibility, but sets a lower bound on the
+value of the symbol. The lower bound is determined by the value of the
+select*ing* symbol. 'select' does not respect visibility, so non-visible
+symbols can be forced to a particular (minimum) value by a select as well.
+
+For non-bool/tristate symbols, it only matters whether the visibility is n or
+non-n: m visibility acts the same as y visibility.
+
+Conditions on 'default' and 'select' work in mostly intuitive ways. If the
+condition is n, the 'default' or 'select' is disabled. If it is m, the
+'default' or 'select' value (the value of the selecting symbol) is truncated
+down to m.
+
+When writing a configuration with Kconfig.write_config(), only symbols that are
+visible, have an (active) default, or are selected will get written out (note
+that this includes all symbols that would accept user values). Kconfiglib
+matches the .config format produced by the C implementations down to the
+character. This eases testing.
+
+For a visible bool/tristate symbol FOO with value n, this line is written to
+.config:
+
+    # CONFIG_FOO is not set
+
+The point is to remember the user n selection (which might differ from the
+default value the symbol would get), while at the same sticking to the rule
+that undefined corresponds to n (.config uses Makefile format, making the line
+above a comment). When the .config file is read back in, this line will be
+treated the same as the following assignment:
+
+    CONFIG_FOO=n
+
+In Kconfiglib, the set of (currently) assignable values for a bool/tristate
+symbol appear in Symbol.assignable. For other symbol types, just check if
+sym.visibility is non-0 (non-n) to see whether the user value will have an
+effect.
+
+
+Intro to the menu tree
+======================
+
+The menu structure, as seen in e.g. menuconfig, is represented by a tree of
+MenuNode objects. The top node of the configuration corresponds to an implicit
+top-level menu, the title of which is shown at the top in the standard
+menuconfig interface. (The title is also available in Kconfig.mainmenu_text in
+Kconfiglib.)
+
+The top node is found in Kconfig.top_node. From there, you can visit child menu
+nodes by following the 'list' pointer, and any following menu nodes by
+following the 'next' pointer. Usually, a non-None 'list' pointer indicates a
+menu or Choice, but menu nodes for symbols can sometimes have a non-None 'list'
+pointer too due to submenus created implicitly from dependencies.
+
+MenuNode.item is either a Symbol or a Choice object, or one of the constants
+MENU and COMMENT. The prompt of the menu node can be found in MenuNode.prompt,
+which also holds the title for menus and comments. For Symbol and Choice,
+MenuNode.help holds the help text (if any, otherwise None).
+
+Most symbols will only have a single menu node. A symbol defined in multiple
+locations will have one menu node for each location. The list of menu nodes for
+a Symbol or Choice can be found in the Symbol/Choice.nodes attribute.
+
+Note that prompts and help texts for symbols and choices are stored in their
+menu node(s) rather than in the Symbol or Choice objects themselves. This makes
+it possible to define a symbol in multiple locations with a different prompt or
+help text in each location. To get the help text or prompt for a symbol with a
+single menu node, do sym.nodes[0].help and sym.nodes[0].prompt, respectively.
+The prompt is a (text, condition) tuple, where condition determines the
+visibility (see 'Intro to expressions' below).
+
+This organization mirrors the C implementation. MenuNode is called
+'struct menu' there, but I thought "menu" was a confusing name.
+
+It is possible to give a Choice a name and define it in multiple locations,
+hence why Choice.nodes is also a list.
+
+As a convenience, the properties added at a particular definition location are
+available on the MenuNode itself, in e.g. MenuNode.defaults. This is helpful
+when generating documentation, so that symbols/choices defined in multiple
+locations can be shown with the correct properties at each location.
+
+
+Intro to expressions
+====================
+
+Expressions can be evaluated with the expr_value() function and printed with
+the expr_str() function (these are used internally as well). Evaluating an
+expression always yields a tristate value, where n, m, and y are represented as
+0, 1, and 2, respectively.
+
+The following table should help you figure out how expressions are represented.
+A, B, C, ... are symbols (Symbol instances), NOT is the kconfiglib.NOT
+constant, etc.
+
+Expression            Representation
+----------            --------------
+A                     A
+"A"                   A (constant symbol)
+!A                    (NOT, A)
+A && B                (AND, A, B)
+A && B && C           (AND, A, (AND, B, C))
+A || B                (OR, A, B)
+A || (B && C && D)    (OR, A, (AND, B, (AND, C, D)))
+A = B                 (EQUAL, A, B)
+A != "foo"            (UNEQUAL, A, foo (constant symbol))
+A && B = C && D       (AND, A, (AND, (EQUAL, B, C), D))
+n                     Kconfig.n (constant symbol)
+m                     Kconfig.m (constant symbol)
+y                     Kconfig.y (constant symbol)
+"y"                   Kconfig.y (constant symbol)
+
+Strings like "foo" in 'default "foo"' or 'depends on SYM = "foo"' are
+represented as constant symbols, so the only values that appear in expressions
+are symbols***. This mirrors the C implementation.
+
+***For choice symbols, the parent Choice will appear in expressions as well,
+but it's usually invisible as the value interfaces of Symbol and Choice are
+identical. This mirrors the C implementation and makes different choice modes
+"just work".
+
+Manual evaluation examples:
+
+  - The value of A && B is min(A.tri_value, B.tri_value)
+
+  - The value of A || B is max(A.tri_value, B.tri_value)
+
+  - The value of !A is 2 - A.tri_value
+
+  - The value of A = B is 2 (y) if A.str_value == B.str_value, and 0 (n)
+    otherwise. Note that str_value is used here instead of tri_value.
+
+    For constant (as well as undefined) symbols, str_value matches the name of
+    the symbol. This mirrors the C implementation and explains why
+    'depends on SYM = "foo"' above works as expected.
+
+n/m/y are automatically converted to the corresponding constant symbols
+"n"/"m"/"y" (Kconfig.n/m/y) during parsing.
+
+Kconfig.const_syms is a dictionary like Kconfig.syms but for constant symbols.
+
+If a condition is missing (e.g., <cond> when the 'if <cond>' is removed from
+'default A if <cond>'), it is actually Kconfig.y. The standard __str__()
+functions just avoid printing 'if y' conditions to give cleaner output.
+
+
+Kconfig extensions
+==================
+
+Kconfiglib includes a couple of Kconfig extensions:
+
+'source' with relative path
+---------------------------
+
+The 'rsource' statement sources Kconfig files with a path relative to directory
+of the Kconfig file containing the 'rsource' statement, instead of relative to
+the project root.
+
+Consider following directory tree:
+
+  Project
+  +--Kconfig
+  |
+  +--src
+     +--Kconfig
+     |
+     +--SubSystem1
+        +--Kconfig
+        |
+        +--ModuleA
+           +--Kconfig
+
+In this example, assume that src/SubSystem1/Kconfig wants to source
+src/SubSystem1/ModuleA/Kconfig.
+
+With 'source', this statement would be used:
+
+  source "src/SubSystem1/ModuleA/Kconfig"
+
+With 'rsource', this turns into
+
+  rsource "ModuleA/Kconfig"
+
+If an absolute path is given to 'rsource', it acts the same as 'source'.
+
+'rsource' can be used to create "position-independent" Kconfig trees that can
+be moved around freely.
+
+
+Globbing 'source'
+-----------------
+
+'source' and 'rsource' accept glob patterns, sourcing all matching Kconfig
+files. They require at least one matching file, raising a KconfigError
+otherwise.
+
+For example, the following statement might source sub1/foofoofoo and
+sub2/foobarfoo:
+
+  source "sub[12]/foo*foo"
+
+The glob patterns accepted are the same as for the standard glob.glob()
+function.
+
+Two additional statements are provided for cases where it's acceptable for a
+pattern to match no files: 'osource' and 'orsource' (the o is for "optional").
+
+For example, the following statements will be no-ops if neither "foo" nor any
+files matching "bar*" exist:
+
+  osource "foo"
+  osource "bar*"
+
+'orsource' does a relative optional source.
+
+'source' and 'osource' are analogous to 'include' and '-include' in Make.
+
+
+Generalized def_* keywords
+--------------------------
+
+def_int, def_hex, and def_string are available in addition to def_bool and
+def_tristate, allowing int, hex, and string symbols to be given a type and a
+default at the same time.
+
+
+Extra optional warnings
+-----------------------
+
+Some optional warnings can be controlled via environment variables:
+
+  - KCONFIG_WARN_UNDEF: If set to 'y', warnings will be generated for all
+    references to undefined symbols within Kconfig files. The only gotcha is
+    that all hex literals must be prefixed with "0x" or "0X", to make it
+    possible to distinguish them from symbol references.
+
+    Some projects (e.g. the Linux kernel) use multiple Kconfig trees with many
+    shared Kconfig files, leading to some safe undefined symbol references.
+    KCONFIG_WARN_UNDEF is useful in projects that only have a single Kconfig
+    tree though.
+
+    KCONFIG_STRICT is an older alias for this environment variable, supported
+    for backwards compatibility.
+
+  - KCONFIG_WARN_UNDEF_ASSIGN: If set to 'y', warnings will be generated for
+    all assignments to undefined symbols within .config files. By default, no
+    such warnings are generated.
+
+    This warning can also be enabled/disabled via the Kconfig.warn_assign_undef
+    variable.
+
+
+Preprocessor user functions defined in Python
+---------------------------------------------
+
+Preprocessor functions can be defined in Python, which makes it simple to
+integrate information from existing Python tools into Kconfig (e.g. to have
+Kconfig symbols depend on hardware information stored in some other format).
+
+Putting a Python module named kconfigfunctions(.py) anywhere in sys.path will
+cause it to be imported by Kconfiglib (in Kconfig.__init__()). Note that
+sys.path can be customized via PYTHONPATH, and includes the directory of the
+module being run by default, as well as installation directories.
+
+If the KCONFIG_FUNCTIONS environment variable is set, it gives a different
+module name to use instead of 'kconfigfunctions'.
+
+The imported module is expected to define a global dictionary named 'functions'
+that maps function names to Python functions, as follows:
+
+  def my_fn(kconf, name, arg_1, arg_2, ...):
+      # kconf:
+      #   Kconfig instance
+      #
+      # name:
+      #   Name of the user-defined function ("my-fn"). Think argv[0].
+      #
+      # arg_1, arg_2, ...:
+      #   Arguments passed to the function from Kconfig (strings)
+      #
+      # Returns a string to be substituted as the result of calling the
+      # function
+      ...
+
+  def my_other_fn(kconf, name, arg_1, arg_2, ...):
+      ...
+
+  functions = {
+      "my-fn":       (my_fn,       <min.args>, <max.args>/None),
+      "my-other-fn": (my_other_fn, <min.args>, <max.args>/None),
+      ...
+  }
+
+  ...
+
+<min.args> and <max.args> are the minimum and maximum number of arguments
+expected by the function (excluding the implicit 'name' argument). If
+<max.args> is None, there is no upper limit to the number of arguments. Passing
+an invalid number of arguments will generate a KconfigError exception.
+
+Functions can access the current parsing location as kconf.filename/linenr.
+Accessing other fields of the Kconfig object is not safe. See the warning
+below.
+
+Keep in mind that for a variable defined like 'foo = $(fn)', 'fn' will be
+called only when 'foo' is expanded. If 'fn' uses the parsing location and the
+intent is to use the location of the assignment, you want 'foo := $(fn)'
+instead, which calls the function immediately.
+
+Once defined, user functions can be called from Kconfig in the same way as
+other preprocessor functions:
+
+    config FOO
+        ...
+        depends on $(my-fn,arg1,arg2)
+
+If my_fn() returns "n", this will result in
+
+    config FOO
+        ...
+        depends on n
+
+Warning
+*******
+
+User-defined preprocessor functions are called as they're encountered at parse
+time, before all Kconfig files have been processed, and before the menu tree
+has been finalized. There are no guarantees that accessing Kconfig symbols or
+the menu tree via the 'kconf' parameter will work, and it could potentially
+lead to a crash.
+
+Preferably, user-defined functions should be stateless.
+
+
+Feedback
+========
+
+Send bug reports, suggestions, and questions to ulfalizer a.t Google's email
+service, or open a ticket on the GitHub page.
+"""
+import errno
+import importlib
+import os
+import re
+import sys
+
+# Get rid of some attribute lookups. These are obvious in context.
+from glob import iglob
+from os.path import dirname, exists, expandvars, islink, join, realpath
+
+
+VERSION = (14, 1, 0)
+
+
+# File layout:
+#
+# Public classes
+# Public functions
+# Internal functions
+# Global constants
+
+# Line length: 79 columns
+
+
+#
+# Public classes
+#
+
+
+class Kconfig(object):
+    """
+    Represents a Kconfig configuration, e.g. for x86 or ARM. This is the set of
+    symbols, choices, and menu nodes appearing in the configuration. Creating
+    any number of Kconfig objects (including for different architectures) is
+    safe. Kconfiglib doesn't keep any global state.
+
+    The following attributes are available. They should be treated as
+    read-only, and some are implemented through @property magic.
+
+    syms:
+      A dictionary with all symbols in the configuration, indexed by name. Also
+      includes all symbols that are referenced in expressions but never
+      defined, except for constant (quoted) symbols.
+
+      Undefined symbols can be recognized by Symbol.nodes being empty -- see
+      the 'Intro to the menu tree' section in the module docstring.
+
+    const_syms:
+      A dictionary like 'syms' for constant (quoted) symbols
+
+    named_choices:
+      A dictionary like 'syms' for named choices (choice FOO)
+
+    defined_syms:
+      A list with all defined symbols, in the same order as they appear in the
+      Kconfig files. Symbols defined in multiple locations appear multiple
+      times.
+
+      Note: You probably want to use 'unique_defined_syms' instead. This
+      attribute is mostly maintained for backwards compatibility.
+
+    unique_defined_syms:
+      A list like 'defined_syms', but with duplicates removed. Just the first
+      instance is kept for symbols defined in multiple locations. Kconfig order
+      is preserved otherwise.
+
+      Using this attribute instead of 'defined_syms' can save work, and
+      automatically gives reasonable behavior when writing configuration output
+      (symbols defined in multiple locations only generate output once, while
+      still preserving Kconfig order for readability).
+
+    choices:
+      A list with all choices, in the same order as they appear in the Kconfig
+      files.
+
+      Note: You probably want to use 'unique_choices' instead. This attribute
+      is mostly maintained for backwards compatibility.
+
+    unique_choices:
+      Analogous to 'unique_defined_syms', for choices. Named choices can have
+      multiple definition locations.
+
+    menus:
+      A list with all menus, in the same order as they appear in the Kconfig
+      files
+
+    comments:
+      A list with all comments, in the same order as they appear in the Kconfig
+      files
+
+    kconfig_filenames:
+      A list with the filenames of all Kconfig files included in the
+      configuration, relative to $srctree (or relative to the current directory
+      if $srctree isn't set), except absolute paths (e.g.
+      'source "/foo/Kconfig"') are kept as-is.
+
+      The files are listed in the order they are source'd, starting with the
+      top-level Kconfig file. If a file is source'd multiple times, it will
+      appear multiple times. Use set() to get unique filenames.
+
+      Note that Kconfig.sync_deps() already indirectly catches any file
+      modifications that change configuration output.
+
+    env_vars:
+      A set() with the names of all environment variables referenced in the
+      Kconfig files.
+
+      Only environment variables referenced with the preprocessor $(FOO) syntax
+      will be registered. The older $FOO syntax is only supported for backwards
+      compatibility.
+
+      Also note that $(FOO) won't be registered unless the environment variable
+      $FOO is actually set. If it isn't, $(FOO) is an expansion of an unset
+      preprocessor variable (which gives the empty string).
+
+      Another gotcha is that environment variables referenced in the values of
+      recursively expanded preprocessor variables (those defined with =) will
+      only be registered if the variable is actually used (expanded) somewhere.
+
+      The note from the 'kconfig_filenames' documentation applies here too.
+
+    n/m/y:
+      The predefined constant symbols n/m/y. Also available in const_syms.
+
+    modules:
+      The Symbol instance for the modules symbol. Currently hardcoded to
+      MODULES, which is backwards compatible. Kconfiglib will warn if
+      'option modules' is set on some other symbol. Tell me if you need proper
+      'option modules' support.
+
+      'modules' is never None. If the MODULES symbol is not explicitly defined,
+      its tri_value will be 0 (n), as expected.
+
+      A simple way to enable modules is to do 'kconf.modules.set_value(2)'
+      (provided the MODULES symbol is defined and visible). Modules are
+      disabled by default in the kernel Kconfig files as of writing, though
+      nearly all defconfig files enable them (with 'CONFIG_MODULES=y').
+
+    defconfig_list:
+      The Symbol instance for the 'option defconfig_list' symbol, or None if no
+      defconfig_list symbol exists. The defconfig filename derived from this
+      symbol can be found in Kconfig.defconfig_filename.
+
+    defconfig_filename:
+      The filename given by the defconfig_list symbol. This is taken from the
+      first 'default' with a satisfied condition where the specified file
+      exists (can be opened for reading). If a defconfig file foo/defconfig is
+      not found and $srctree was set when the Kconfig was created,
+      $srctree/foo/defconfig is looked up as well.
+
+      'defconfig_filename' is None if either no defconfig_list symbol exists,
+      or if the defconfig_list symbol has no 'default' with a satisfied
+      condition that specifies a file that exists.
+
+      Gotcha: scripts/kconfig/Makefile might pass --defconfig=<defconfig> to
+      scripts/kconfig/conf when running e.g. 'make defconfig'. This option
+      overrides the defconfig_list symbol, meaning defconfig_filename might not
+      always match what 'make defconfig' would use.
+
+    top_node:
+      The menu node (see the MenuNode class) of the implicit top-level menu.
+      Acts as the root of the menu tree.
+
+    mainmenu_text:
+      The prompt (title) of the top menu (top_node). Defaults to "Main menu".
+      Can be changed with the 'mainmenu' statement (see kconfig-language.txt).
+
+    variables:
+      A dictionary with all preprocessor variables, indexed by name. See the
+      Variable class.
+
+    warn:
+      Set this variable to True/False to enable/disable warnings. See
+      Kconfig.__init__().
+
+      When 'warn' is False, the values of the other warning-related variables
+      are ignored.
+
+      This variable as well as the other warn* variables can be read to check
+      the current warning settings.
+
+    warn_to_stderr:
+      Set this variable to True/False to enable/disable warnings on stderr. See
+      Kconfig.__init__().
+
+    warn_assign_undef:
+      Set this variable to True to generate warnings for assignments to
+      undefined symbols in configuration files.
+
+      This variable is False by default unless the KCONFIG_WARN_UNDEF_ASSIGN
+      environment variable was set to 'y' when the Kconfig instance was
+      created.
+
+    warn_assign_override:
+      Set this variable to True to generate warnings for multiple assignments
+      to the same symbol in configuration files, where the assignments set
+      different values (e.g. CONFIG_FOO=m followed by CONFIG_FOO=y, where the
+      last value would get used).
+
+      This variable is True by default. Disabling it might be useful when
+      merging configurations.
+
+    warn_assign_redun:
+      Like warn_assign_override, but for multiple assignments setting a symbol
+      to the same value.
+
+      This variable is True by default. Disabling it might be useful when
+      merging configurations.
+
+    warnings:
+      A list of strings containing all warnings that have been generated, for
+      cases where more flexibility is needed.
+
+      See the 'warn_to_stderr' parameter to Kconfig.__init__() and the
+      Kconfig.warn_to_stderr variable as well. Note that warnings still get
+      added to Kconfig.warnings when 'warn_to_stderr' is True.
+
+      Just as for warnings printed to stderr, only warnings that are enabled
+      will get added to Kconfig.warnings. See the various Kconfig.warn*
+      variables.
+
+    missing_syms:
+      A list with (name, value) tuples for all assignments to undefined symbols
+      within the most recently loaded .config file(s). 'name' is the symbol
+      name without the 'CONFIG_' prefix. 'value' is a string that gives the
+      right-hand side of the assignment verbatim.
+
+      See Kconfig.load_config() as well.
+
+    srctree:
+      The value the $srctree environment variable had when the Kconfig instance
+      was created, or the empty string if $srctree wasn't set. This gives nice
+      behavior with os.path.join(), which treats "" as the current directory,
+      without adding "./".
+
+      Kconfig files are looked up relative to $srctree (unless absolute paths
+      are used), and .config files are looked up relative to $srctree if they
+      are not found in the current directory. This is used to support
+      out-of-tree builds. The C tools use this environment variable in the same
+      way.
+
+      Changing $srctree after creating the Kconfig instance has no effect. Only
+      the value when the configuration is loaded matters. This avoids surprises
+      if multiple configurations are loaded with different values for $srctree.
+
+    config_prefix:
+      The value the CONFIG_ environment variable had when the Kconfig instance
+      was created, or "CONFIG_" if CONFIG_ wasn't set. This is the prefix used
+      (and expected) on symbol names in .config files and C headers. Used in
+      the same way in the C tools.
+
+    config_header:
+      The value the KCONFIG_CONFIG_HEADER environment variable had when the
+      Kconfig instance was created, or the empty string if
+      KCONFIG_CONFIG_HEADER wasn't set. This string is inserted verbatim at the
+      beginning of configuration files. See write_config().
+
+    header_header:
+      The value the KCONFIG_AUTOHEADER_HEADER environment variable had when the
+      Kconfig instance was created, or the empty string if
+      KCONFIG_AUTOHEADER_HEADER wasn't set. This string is inserted verbatim at
+      the beginning of header files. See write_autoconf().
+
+    filename/linenr:
+      The current parsing location, for use in Python preprocessor functions.
+      See the module docstring.
+    """
+    __slots__ = (
+        "_encoding",
+        "_functions",
+        "_set_match",
+        "_srctree_prefix",
+        "_unset_match",
+        "_warn_assign_no_prompt",
+        "choices",
+        "comments",
+        "config_header",
+        "config_prefix",
+        "const_syms",
+        "defconfig_list",
+        "defined_syms",
+        "env_vars",
+        "header_header",
+        "kconfig_filenames",
+        "m",
+        "menus",
+        "missing_syms",
+        "modules",
+        "n",
+        "named_choices",
+        "srctree",
+        "syms",
+        "top_node",
+        "unique_choices",
+        "unique_defined_syms",
+        "variables",
+        "warn",
+        "warn_assign_override",
+        "warn_assign_redun",
+        "warn_assign_undef",
+        "warn_to_stderr",
+        "warnings",
+        "y",
+
+        # Parsing-related
+        "_parsing_kconfigs",
+        "_readline",
+        "filename",
+        "linenr",
+        "_include_path",
+        "_filestack",
+        "_line",
+        "_tokens",
+        "_tokens_i",
+        "_reuse_tokens",
+    )
+
+    #
+    # Public interface
+    #
+
+    def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True,
+                 encoding="utf-8", suppress_traceback=False):
+        """
+        Creates a new Kconfig object by parsing Kconfig files.
+        Note that Kconfig files are not the same as .config files (which store
+        configuration symbol values).
+
+        See the module docstring for some environment variables that influence
+        default warning settings (KCONFIG_WARN_UNDEF and
+        KCONFIG_WARN_UNDEF_ASSIGN).
+
+        Raises KconfigError on syntax/semantic errors, and OSError or (possibly
+        a subclass of) IOError on IO errors ('errno', 'strerror', and
+        'filename' are available). Note that IOError is an alias for OSError on
+        Python 3, so it's enough to catch OSError there. If you need Python 2/3
+        compatibility, it's easiest to catch EnvironmentError, which is a
+        common base class of OSError/IOError on Python 2 and an alias for
+        OSError on Python 3.
+
+        filename (default: "Kconfig"):
+          The Kconfig file to load. For the Linux kernel, you'll want "Kconfig"
+          from the top-level directory, as environment variables will make sure
+          the right Kconfig is included from there (arch/$SRCARCH/Kconfig as of
+          writing).
+
+          If $srctree is set, 'filename' will be looked up relative to it.
+          $srctree is also used to look up source'd files within Kconfig files.
+          See the class documentation.
+
+          If you are using Kconfiglib via 'make scriptconfig', the filename of
+          the base base Kconfig file will be in sys.argv[1]. It's currently
+          always "Kconfig" in practice.
+
+        warn (default: True):
+          True if warnings related to this configuration should be generated.
+          This can be changed later by setting Kconfig.warn to True/False. It
+          is provided as a constructor argument since warnings might be
+          generated during parsing.
+
+          See the other Kconfig.warn_* variables as well, which enable or
+          suppress certain warnings when warnings are enabled.
+
+          All generated warnings are added to the Kconfig.warnings list. See
+          the class documentation.
+
+        warn_to_stderr (default: True):
+          True if warnings should be printed to stderr in addition to being
+          added to Kconfig.warnings.
+
+          This can be changed later by setting Kconfig.warn_to_stderr to
+          True/False.
+
+        encoding (default: "utf-8"):
+          The encoding to use when reading and writing files, and when decoding
+          output from commands run via $(shell). If None, the encoding
+          specified in the current locale will be used.
+
+          The "utf-8" default avoids exceptions on systems that are configured
+          to use the C locale, which implies an ASCII encoding.
+
+          This parameter has no effect on Python 2, due to implementation
+          issues (regular strings turning into Unicode strings, which are
+          distinct in Python 2). Python 2 doesn't decode regular strings
+          anyway.
+
+          Related PEP: https://www.python.org/dev/peps/pep-0538/
+
+        suppress_traceback (default: False):
+          Helper for tools. When True, any EnvironmentError or KconfigError
+          generated during parsing is caught, the exception message is printed
+          to stderr together with the command name, and sys.exit(1) is called
+          (which generates SystemExit).
+
+          This hides the Python traceback for "expected" errors like syntax
+          errors in Kconfig files.
+
+          Other exceptions besides EnvironmentError and KconfigError are still
+          propagated when suppress_traceback is True.
+        """
+        try:
+            self._init(filename, warn, warn_to_stderr, encoding)
+        except (EnvironmentError, KconfigError) as e:
+            if suppress_traceback:
+                cmd = sys.argv[0]  # Empty string if missing
+                if cmd:
+                    cmd += ": "
+                # Some long exception messages have extra newlines for better
+                # formatting when reported as an unhandled exception. Strip
+                # them here.
+                sys.exit(cmd + str(e).strip())
+            raise
+
+    def _init(self, filename, warn, warn_to_stderr, encoding):
+        # See __init__()
+
+        self._encoding = encoding
+
+        self.srctree = os.getenv("srctree", "")
+        # A prefix we can reliably strip from glob() results to get a filename
+        # relative to $srctree. relpath() can cause issues for symlinks,
+        # because it assumes symlink/../foo is the same as foo/.
+        self._srctree_prefix = realpath(self.srctree) + os.sep
+
+        self.warn = warn
+        self.warn_to_stderr = warn_to_stderr
+        self.warn_assign_undef = os.getenv("KCONFIG_WARN_UNDEF_ASSIGN") == "y"
+        self.warn_assign_override = True
+        self.warn_assign_redun = True
+        self._warn_assign_no_prompt = True
+
+        self.warnings = []
+
+        self.config_prefix = os.getenv("CONFIG_", "CONFIG_")
+        # Regular expressions for parsing .config files
+        self._set_match = _re_match(self.config_prefix + r"([^=]+)=(.*)")
+        self._unset_match = _re_match(r"# {}([^ ]+) is not set".format(
+            self.config_prefix))
+
+        self.config_header = os.getenv("KCONFIG_CONFIG_HEADER", "")
+        self.header_header = os.getenv("KCONFIG_AUTOHEADER_HEADER", "")
+
+        self.syms = {}
+        self.const_syms = {}
+        self.defined_syms = []
+        self.missing_syms = []
+        self.named_choices = {}
+        self.choices = []
+        self.menus = []
+        self.comments = []
+
+        for nmy in "n", "m", "y":
+            sym = Symbol()
+            sym.kconfig = self
+            sym.name = nmy
+            sym.is_constant = True
+            sym.orig_type = TRISTATE
+            sym._cached_tri_val = STR_TO_TRI[nmy]
+
+            self.const_syms[nmy] = sym
+
+        self.n = self.const_syms["n"]
+        self.m = self.const_syms["m"]
+        self.y = self.const_syms["y"]
+
+        # Make n/m/y well-formed symbols
+        for nmy in "n", "m", "y":
+            sym = self.const_syms[nmy]
+            sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+        # Maps preprocessor variables names to Variable instances
+        self.variables = {}
+
+        # Predefined preprocessor functions, with min/max number of arguments
+        self._functions = {
+            "info":       (_info_fn,       1, 1),
+            "error-if":   (_error_if_fn,   2, 2),
+            "filename":   (_filename_fn,   0, 0),
+            "lineno":     (_lineno_fn,     0, 0),
+            "shell":      (_shell_fn,      1, 1),
+            "warning-if": (_warning_if_fn, 2, 2),
+        }
+
+        # Add any user-defined preprocessor functions
+        try:
+            self._functions.update(
+                importlib.import_module(
+                    os.getenv("KCONFIG_FUNCTIONS", "kconfigfunctions")
+                ).functions)
+        except ImportError:
+            pass
+
+        # This determines whether previously unseen symbols are registered.
+        # They shouldn't be if we parse expressions after parsing, as part of
+        # Kconfig.eval_string().
+        self._parsing_kconfigs = True
+
+        self.modules = self._lookup_sym("MODULES")
+        self.defconfig_list = None
+
+        self.top_node = MenuNode()
+        self.top_node.kconfig = self
+        self.top_node.item = MENU
+        self.top_node.is_menuconfig = True
+        self.top_node.visibility = self.y
+        self.top_node.prompt = ("Main menu", self.y)
+        self.top_node.parent = None
+        self.top_node.dep = self.y
+        self.top_node.filename = filename
+        self.top_node.linenr = 1
+        self.top_node.include_path = ()
+
+        # Parse the Kconfig files
+
+        # Not used internally. Provided as a convenience.
+        self.kconfig_filenames = [filename]
+        self.env_vars = set()
+
+        # Keeps track of the location in the parent Kconfig files. Kconfig
+        # files usually source other Kconfig files. See _enter_file().
+        self._filestack = []
+        self._include_path = ()
+
+        # The current parsing location
+        self.filename = filename
+        self.linenr = 0
+
+        # Used to avoid retokenizing lines when we discover that they're not
+        # part of the construct currently being parsed. This is kinda like an
+        # unget operation.
+        self._reuse_tokens = False
+
+        # Open the top-level Kconfig file. Store the readline() method directly
+        # as a small optimization.
+        self._readline = self._open(join(self.srctree, filename), "r").readline
+
+        try:
+            # Parse the Kconfig files. Returns the last node, which we
+            # terminate with '.next = None'.
+            self._parse_block(None, self.top_node, self.top_node).next = None
+            self.top_node.list = self.top_node.next
+            self.top_node.next = None
+        except UnicodeDecodeError as e:
+            _decoding_error(e, self.filename)
+
+        # Close the top-level Kconfig file. __self__ fetches the 'file' object
+        # for the method.
+        self._readline.__self__.close()
+
+        self._parsing_kconfigs = False
+
+        # Do various menu tree post-processing
+        self._finalize_node(self.top_node, self.y)
+
+        self.unique_defined_syms = _ordered_unique(self.defined_syms)
+        self.unique_choices = _ordered_unique(self.choices)
+
+        # Do sanity checks. Some of these depend on everything being finalized.
+        self._check_sym_sanity()
+        self._check_choice_sanity()
+
+        # KCONFIG_STRICT is an older alias for KCONFIG_WARN_UNDEF, supported
+        # for backwards compatibility
+        if os.getenv("KCONFIG_WARN_UNDEF") == "y" or \
+           os.getenv("KCONFIG_STRICT") == "y":
+
+            self._check_undef_syms()
+
+        # Build Symbol._dependents for all symbols and choices
+        self._build_dep()
+
+        # Check for dependency loops
+        check_dep_loop_sym = _check_dep_loop_sym  # Micro-optimization
+        for sym in self.unique_defined_syms:
+            check_dep_loop_sym(sym, False)
+
+        # Add extra dependencies from choices to choice symbols that get
+        # awkward during dependency loop detection
+        self._add_choice_deps()
+
+    @property
+    def mainmenu_text(self):
+        """
+        See the class documentation.
+        """
+        return self.top_node.prompt[0]
+
+    @property
+    def defconfig_filename(self):
+        """
+        See the class documentation.
+        """
+        if self.defconfig_list:
+            for filename, cond in self.defconfig_list.defaults:
+                if expr_value(cond):
+                    try:
+                        with self._open_config(filename.str_value) as f:
+                            return f.name
+                    except EnvironmentError:
+                        continue
+
+        return None
+
+    def load_config(self, filename=None, replace=True, verbose=None):
+        """
+        Loads symbol values from a file in the .config format. Equivalent to
+        calling Symbol.set_value() to set each of the values.
+
+        "# CONFIG_FOO is not set" within a .config file sets the user value of
+        FOO to n. The C tools work the same way.
+
+        For each symbol, the Symbol.user_value attribute holds the value the
+        symbol was assigned in the .config file (if any). The user value might
+        differ from Symbol.str/tri_value if there are unsatisfied dependencies.
+
+        Calling this function also updates the Kconfig.missing_syms attribute
+        with a list of all assignments to undefined symbols within the
+        configuration file. Kconfig.missing_syms is cleared if 'replace' is
+        True, and appended to otherwise. See the documentation for
+        Kconfig.missing_syms as well.
+
+        See the Kconfig.__init__() docstring for raised exceptions
+        (OSError/IOError). KconfigError is never raised here.
+
+        filename (default: None):
+          Path to load configuration from (a string). Respects $srctree if set
+          (see the class documentation).
+
+          If 'filename' is None (the default), the configuration file to load
+          (if any) is calculated automatically, giving the behavior you'd
+          usually want:
+
+            1. If the KCONFIG_CONFIG environment variable is set, it gives the
+               path to the configuration file to load. Otherwise, ".config" is
+               used. See standard_config_filename().
+
+            2. If the path from (1.) doesn't exist, the configuration file
+               given by kconf.defconfig_filename is loaded instead, which is
+               derived from the 'option defconfig_list' symbol.
+
+            3. If (1.) and (2.) fail to find a configuration file to load, no
+               configuration file is loaded, and symbols retain their current
+               values (e.g., their default values). This is not an error.
+
+           See the return value as well.
+
+        replace (default: True):
+          If True, all existing user values will be cleared before loading the
+          .config. Pass False to merge configurations.
+
+        verbose (default: None):
+          Limited backwards compatibility to prevent crashes. A warning is
+          printed if anything but None is passed.
+
+          Prior to Kconfiglib 12.0.0, this option enabled printing of messages
+          to stdout when 'filename' was None. A message is (always) returned
+          now instead, which is more flexible.
+
+          Will probably be removed in some future version.
+
+        Returns a string with a message saying which file got loaded (or
+        possibly that no file got loaded, when 'filename' is None). This is
+        meant to reduce boilerplate in tools, which can do e.g.
+        print(kconf.load_config()). The returned message distinguishes between
+        loading (replace == True) and merging (replace == False).
+        """
+        if verbose is not None:
+            _warn_verbose_deprecated("load_config")
+
+        msg = None
+        if filename is None:
+            filename = standard_config_filename()
+            if not exists(filename) and \
+               not exists(join(self.srctree, filename)):
+                defconfig = self.defconfig_filename
+                if defconfig is None:
+                    return "Using default symbol values (no '{}')" \
+                           .format(filename)
+
+                msg = " default configuration '{}' (no '{}')" \
+                      .format(defconfig, filename)
+                filename = defconfig
+
+        if not msg:
+            msg = " configuration '{}'".format(filename)
+
+        # Disable the warning about assigning to symbols without prompts. This
+        # is normal and expected within a .config file.
+        self._warn_assign_no_prompt = False
+
+        # This stub only exists to make sure _warn_assign_no_prompt gets
+        # reenabled
+        try:
+            self._load_config(filename, replace)
+        except UnicodeDecodeError as e:
+            _decoding_error(e, filename)
+        finally:
+            self._warn_assign_no_prompt = True
+
+        return ("Loaded" if replace else "Merged") + msg
+
+    def _load_config(self, filename, replace):
+        with self._open_config(filename) as f:
+            if replace:
+                self.missing_syms = []
+
+                # If we're replacing the configuration, keep track of which
+                # symbols and choices got set so that we can unset the rest
+                # later. This avoids invalidating everything and is faster.
+                # Another benefit is that invalidation must be rock solid for
+                # it to work, making it a good test.
+
+                for sym in self.unique_defined_syms:
+                    sym._was_set = False
+
+                for choice in self.unique_choices:
+                    choice._was_set = False
+
+            # Small optimizations
+            set_match = self._set_match
+            unset_match = self._unset_match
+            get_sym = self.syms.get
+
+            for linenr, line in enumerate(f, 1):
+                # The C tools ignore trailing whitespace
+                line = line.rstrip()
+
+                match = set_match(line)
+                if match:
+                    name, val = match.groups()
+                    sym = get_sym(name)
+                    if not sym or not sym.nodes:
+                        self._undef_assign(name, val, filename, linenr)
+                        continue
+
+                    if sym.orig_type in _BOOL_TRISTATE:
+                        # The C implementation only checks the first character
+                        # to the right of '=', for whatever reason
+                        if not (sym.orig_type is BOOL
+                                and val.startswith(("y", "n")) or
+                                sym.orig_type is TRISTATE
+                                and val.startswith(("y", "m", "n"))):
+                            self._warn("'{}' is not a valid value for the {} "
+                                       "symbol {}. Assignment ignored."
+                                       .format(val, TYPE_TO_STR[sym.orig_type],
+                                               sym.name_and_loc),
+                                       filename, linenr)
+                            continue
+
+                        val = val[0]
+
+                        if sym.choice and val != "n":
+                            # During .config loading, we infer the mode of the
+                            # choice from the kind of values that are assigned
+                            # to the choice symbols
+
+                            prev_mode = sym.choice.user_value
+                            if prev_mode is not None and \
+                               TRI_TO_STR[prev_mode] != val:
+
+                                self._warn("both m and y assigned to symbols "
+                                           "within the same choice",
+                                           filename, linenr)
+
+                            # Set the choice's mode
+                            sym.choice.set_value(val)
+
+                    elif sym.orig_type is STRING:
+                        match = _conf_string_match(val)
+                        if not match:
+                            self._warn("malformed string literal in "
+                                       "assignment to {}. Assignment ignored."
+                                       .format(sym.name_and_loc),
+                                       filename, linenr)
+                            continue
+
+                        val = unescape(match.group(1))
+
+                else:
+                    match = unset_match(line)
+                    if not match:
+                        # Print a warning for lines that match neither
+                        # set_match() nor unset_match() and that are not blank
+                        # lines or comments. 'line' has already been
+                        # rstrip()'d, so blank lines show up as "" here.
+                        if line and not line.lstrip().startswith("#"):
+                            self._warn("ignoring malformed line '{}'"
+                                       .format(line),
+                                       filename, linenr)
+
+                        continue
+
+                    name = match.group(1)
+                    sym = get_sym(name)
+                    if not sym or not sym.nodes:
+                        self._undef_assign(name, "n", filename, linenr)
+                        continue
+
+                    if sym.orig_type not in _BOOL_TRISTATE:
+                        continue
+
+                    val = "n"
+
+                # Done parsing the assignment. Set the value.
+
+                if sym._was_set:
+                    self._assigned_twice(sym, val, filename, linenr)
+
+                sym.set_value(val)
+
+        if replace:
+            # If we're replacing the configuration, unset the symbols that
+            # didn't get set
+
+            for sym in self.unique_defined_syms:
+                if not sym._was_set:
+                    sym.unset_value()
+
+            for choice in self.unique_choices:
+                if not choice._was_set:
+                    choice.unset_value()
+
+    def _undef_assign(self, name, val, filename, linenr):
+        # Called for assignments to undefined symbols during .config loading
+
+        self.missing_syms.append((name, val))
+        if self.warn_assign_undef:
+            self._warn(
+                "attempt to assign the value '{}' to the undefined symbol {}"
+                .format(val, name), filename, linenr)
+
+    def _assigned_twice(self, sym, new_val, filename, linenr):
+        # Called when a symbol is assigned more than once in a .config file
+
+        # Use strings for bool/tristate user values in the warning
+        if sym.orig_type in _BOOL_TRISTATE:
+            user_val = TRI_TO_STR[sym.user_value]
+        else:
+            user_val = sym.user_value
+
+        msg = '{} set more than once. Old value "{}", new value "{}".'.format(
+            sym.name_and_loc, user_val, new_val)
+
+        if user_val == new_val:
+            if self.warn_assign_redun:
+                self._warn(msg, filename, linenr)
+        elif self.warn_assign_override:
+            self._warn(msg, filename, linenr)
+
+    def load_allconfig(self, filename):
+        """
+        Helper for all*config. Loads (merges) the configuration file specified
+        by KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in
+        the Linux kernel.
+
+        Disables warnings for duplicated assignments within configuration files
+        for the duration of the call
+        (kconf.warn_assign_override/warn_assign_redun = False), and restores
+        the previous warning settings at the end. The KCONFIG_ALLCONFIG
+        configuration file is expected to override symbols.
+
+        Exits with sys.exit() (which raises a SystemExit exception) and prints
+        an error to stderr if KCONFIG_ALLCONFIG is set but the configuration
+        file can't be opened.
+
+        filename:
+          Command-specific configuration filename - "allyes.config",
+          "allno.config", etc.
+        """
+        load_allconfig(self, filename)
+
+    def write_autoconf(self, filename=None, header=None):
+        r"""
+        Writes out symbol values as a C header file, matching the format used
+        by include/generated/autoconf.h in the kernel.
+
+        The ordering of the #defines matches the one generated by
+        write_config(). The order in the C implementation depends on the hash
+        table implementation as of writing, and so won't match.
+
+        If 'filename' exists and its contents is identical to what would get
+        written out, it is left untouched. This avoids updating file metadata
+        like the modification time and possibly triggering redundant work in
+        build tools.
+
+        filename (default: None):
+          Path to write header to.
+
+          If None (the default), the path in the environment variable
+          KCONFIG_AUTOHEADER is used if set, and "include/generated/autoconf.h"
+          otherwise. This is compatible with the C tools.
+
+        header (default: None):
+          Text inserted verbatim at the beginning of the file. You would
+          usually want it enclosed in '/* */' to make it a C comment, and
+          include a trailing newline.
+
+          If None (the default), the value of the environment variable
+          KCONFIG_AUTOHEADER_HEADER had when the Kconfig instance was created
+          will be used if it was set, and no header otherwise. See the
+          Kconfig.header_header attribute.
+
+        Returns a string with a message saying that the header got saved, or
+        that there were no changes to it. This is meant to reduce boilerplate
+        in tools, which can do e.g. print(kconf.write_autoconf()).
+        """
+        if filename is None:
+            filename = os.getenv("KCONFIG_AUTOHEADER",
+                                 "include/generated/autoconf.h")
+
+        if self._write_if_changed(filename, self._autoconf_contents(header)):
+            return "Kconfig header saved to '{}'".format(filename)
+        return "No change to Kconfig header in '{}'".format(filename)
+
+    def _autoconf_contents(self, header):
+        # write_autoconf() helper. Returns the contents to write as a string,
+        # with 'header' or KCONFIG_AUTOHEADER_HEADER at the beginning.
+
+        if header is None:
+            header = self.header_header
+
+        chunks = [header]  # "".join()ed later
+        add = chunks.append
+
+        for sym in self.unique_defined_syms:
+            # _write_to_conf is determined when the value is calculated. This
+            # is a hidden function call due to property magic.
+            #
+            # Note: In client code, you can check if sym.config_string is empty
+            # instead, to avoid accessing the internal _write_to_conf variable
+            # (though it's likely to keep working).
+            val = sym.str_value
+            if not sym._write_to_conf:
+                continue
+
+            if sym.orig_type in _BOOL_TRISTATE:
+                if val == "y":
+                    add("#define {}{} 1\n"
+                        .format(self.config_prefix, sym.name))
+                elif val == "m":
+                    add("#define {}{}_MODULE 1\n"
+                        .format(self.config_prefix, sym.name))
+
+            elif sym.orig_type is STRING:
+                add('#define {}{} "{}"\n'
+                    .format(self.config_prefix, sym.name, escape(val)))
+
+            else:  # sym.orig_type in _INT_HEX:
+                if sym.orig_type is HEX and \
+                   not val.startswith(("0x", "0X")):
+                    val = "0x" + val
+
+                add("#define {}{} {}\n"
+                    .format(self.config_prefix, sym.name, val))
+
+        return "".join(chunks)
+
+    def write_config(self, filename=None, header=None, save_old=True,
+                     verbose=None):
+        r"""
+        Writes out symbol values in the .config format. The format matches the
+        C implementation, including ordering.
+
+        Symbols appear in the same order in generated .config files as they do
+        in the Kconfig files. For symbols defined in multiple locations, a
+        single assignment is written out corresponding to the first location
+        where the symbol is defined.
+
+        See the 'Intro to symbol values' section in the module docstring to
+        understand which symbols get written out.
+
+        If 'filename' exists and its contents is identical to what would get
+        written out, it is left untouched. This avoids updating file metadata
+        like the modification time and possibly triggering redundant work in
+        build tools.
+
+        See the Kconfig.__init__() docstring for raised exceptions
+        (OSError/IOError). KconfigError is never raised here.
+
+        filename (default: None):
+          Path to write configuration to (a string).
+
+          If None (the default), the path in the environment variable
+          KCONFIG_CONFIG is used if set, and ".config" otherwise. See
+          standard_config_filename().
+
+        header (default: None):
+          Text inserted verbatim at the beginning of the file. You would
+          usually want each line to start with '#' to make it a comment, and
+          include a trailing newline.
+
+          if None (the default), the value of the environment variable
+          KCONFIG_CONFIG_HEADER had when the Kconfig instance was created will
+          be used if it was set, and no header otherwise. See the
+          Kconfig.config_header attribute.
+
+        save_old (default: True):
+          If True and <filename> already exists, a copy of it will be saved to
+          <filename>.old in the same directory before the new configuration is
+          written.
+
+          Errors are silently ignored if <filename>.old cannot be written (e.g.
+          due to being a directory, or <filename> being something like
+          /dev/null).
+
+        verbose (default: None):
+          Limited backwards compatibility to prevent crashes. A warning is
+          printed if anything but None is passed.
+
+          Prior to Kconfiglib 12.0.0, this option enabled printing of messages
+          to stdout when 'filename' was None. A message is (always) returned
+          now instead, which is more flexible.
+
+          Will probably be removed in some future version.
+
+        Returns a string with a message saying which file got saved. This is
+        meant to reduce boilerplate in tools, which can do e.g.
+        print(kconf.write_config()).
+        """
+        if verbose is not None:
+            _warn_verbose_deprecated("write_config")
+
+        if filename is None:
+            filename = standard_config_filename()
+
+        contents = self._config_contents(header)
+        if self._contents_eq(filename, contents):
+            return "No change to configuration in '{}'".format(filename)
+
+        if save_old:
+            _save_old(filename)
+
+        with self._open(filename, "w") as f:
+            f.write(contents)
+
+        return "Configuration saved to '{}'".format(filename)
+
+    def _config_contents(self, header):
+        # write_config() helper. Returns the contents to write as a string,
+        # with 'header' or KCONFIG_CONFIG_HEADER at the beginning.
+        #
+        # More memory friendly would be to 'yield' the strings and
+        # "".join(_config_contents()), but it was a bit slower on my system.
+
+        # node_iter() was used here before commit 3aea9f7 ("Add '# end of
+        # <menu>' after menus in .config"). Those comments get tricky to
+        # implement with it.
+
+        for sym in self.unique_defined_syms:
+            sym._visited = False
+
+        if header is None:
+            header = self.config_header
+
+        chunks = [header]  # "".join()ed later
+        add = chunks.append
+
+        # Did we just print an '# end of ...' comment?
+        after_end_comment = False
+
+        node = self.top_node
+        while 1:
+            # Jump to the next node with an iterative tree walk
+            if node.list:
+                node = node.list
+            elif node.next:
+                node = node.next
+            else:
+                while node.parent:
+                    node = node.parent
+
+                    # Add a comment when leaving visible menus
+                    if node.item is MENU and expr_value(node.dep) and \
+                       expr_value(node.visibility) and \
+                       node is not self.top_node:
+                        add("# end of {}\n".format(node.prompt[0]))
+                        after_end_comment = True
+
+                    if node.next:
+                        node = node.next
+                        break
+                else:
+                    # No more nodes
+                    return "".join(chunks)
+
+            # Generate configuration output for the node
+
+            item = node.item
+
+            if item.__class__ is Symbol:
+                if item._visited:
+                    continue
+                item._visited = True
+
+                conf_string = item.config_string
+                if not conf_string:
+                    continue
+
+                if after_end_comment:
+                    # Add a blank line before the first symbol printed after an
+                    # '# end of ...' comment
+                    after_end_comment = False
+                    add("\n")
+                add(conf_string)
+
+            elif expr_value(node.dep) and \
+                 ((item is MENU and expr_value(node.visibility)) or
+                  item is COMMENT):
+
+                add("\n#\n# {}\n#\n".format(node.prompt[0]))
+                after_end_comment = False
+
+    def write_min_config(self, filename, header=None):
+        """
+        Writes out a "minimal" configuration file, omitting symbols whose value
+        matches their default value. The format matches the one produced by
+        'make savedefconfig'.
+
+        The resulting configuration file is incomplete, but a complete
+        configuration can be derived from it by loading it. Minimal
+        configuration files can serve as a more manageable configuration format
+        compared to a "full" .config file, especially when configurations files
+        are merged or edited by hand.
+
+        See the Kconfig.__init__() docstring for raised exceptions
+        (OSError/IOError). KconfigError is never raised here.
+
+        filename:
+          Path to write minimal configuration to.
+
+        header (default: None):
+          Text inserted verbatim at the beginning of the file. You would
+          usually want each line to start with '#' to make it a comment, and
+          include a final terminating newline.
+
+          if None (the default), the value of the environment variable
+          KCONFIG_CONFIG_HEADER had when the Kconfig instance was created will
+          be used if it was set, and no header otherwise. See the
+          Kconfig.config_header attribute.
+
+        Returns a string with a message saying the minimal configuration got
+        saved, or that there were no changes to it. This is meant to reduce
+        boilerplate in tools, which can do e.g.
+        print(kconf.write_min_config()).
+        """
+        if self._write_if_changed(filename, self._min_config_contents(header)):
+            return "Minimal configuration saved to '{}'".format(filename)
+        return "No change to minimal configuration in '{}'".format(filename)
+
+    def _min_config_contents(self, header):
+        # write_min_config() helper. Returns the contents to write as a string,
+        # with 'header' or KCONFIG_CONFIG_HEADER at the beginning.
+
+        if header is None:
+            header = self.config_header
+
+        chunks = [header]  # "".join()ed later
+        add = chunks.append
+
+        for sym in self.unique_defined_syms:
+            # Skip symbols that cannot be changed. Only check
+            # non-choice symbols, as selects don't affect choice
+            # symbols.
+            if not sym.choice and \
+               sym.visibility <= expr_value(sym.rev_dep):
+                continue
+
+            # Skip symbols whose value matches their default
+            if sym.str_value == sym._str_default():
+                continue
+
+            # Skip symbols that would be selected by default in a
+            # choice, unless the choice is optional or the symbol type
+            # isn't bool (it might be possible to set the choice mode
+            # to n or the symbol to m in those cases).
+            if sym.choice and \
+               not sym.choice.is_optional and \
+               sym.choice._selection_from_defaults() is sym and \
+               sym.orig_type is BOOL and \
+               sym.tri_value == 2:
+                continue
+
+            add(sym.config_string)
+
+        return "".join(chunks)
+
+    def sync_deps(self, path):
+        """
+        Creates or updates a directory structure that can be used to avoid
+        doing a full rebuild whenever the configuration is changed, mirroring
+        include/config/ in the kernel.
+
+        This function is intended to be called during each build, before
+        compiling source files that depend on configuration symbols.
+
+        See the Kconfig.__init__() docstring for raised exceptions
+        (OSError/IOError). KconfigError is never raised here.
+
+        path:
+          Path to directory
+
+        sync_deps(path) does the following:
+
+          1. If the directory <path> does not exist, it is created.
+
+          2. If <path>/auto.conf exists, old symbol values are loaded from it,
+             which are then compared against the current symbol values. If a
+             symbol has changed value (would generate different output in
+             autoconf.h compared to before), the change is signaled by
+             touch'ing a file corresponding to the symbol.
+
+             The first time sync_deps() is run on a directory, <path>/auto.conf
+             won't exist, and no old symbol values will be available. This
+             logically has the same effect as updating the entire
+             configuration.
+
+             The path to a symbol's file is calculated from the symbol's name
+             by replacing all '_' with '/' and appending '.h'. For example, the
+             symbol FOO_BAR_BAZ gets the file <path>/foo/bar/baz.h, and FOO
+             gets the file <path>/foo.h.
+
+             This scheme matches the C tools. The point is to avoid having a
+             single directory with a huge number of files, which the underlying
+             filesystem might not handle well.
+
+          3. A new auto.conf with the current symbol values is written, to keep
+             track of them for the next build.
+
+             If auto.conf exists and its contents is identical to what would
+             get written out, it is left untouched. This avoids updating file
+             metadata like the modification time and possibly triggering
+             redundant work in build tools.
+
+
+        The last piece of the puzzle is knowing what symbols each source file
+        depends on. Knowing that, dependencies can be added from source files
+        to the files corresponding to the symbols they depends on. The source
+        file will then get recompiled (only) when the symbol value changes
+        (provided sync_deps() is run first during each build).
+
+        The tool in the kernel that extracts symbol dependencies from source
+        files is scripts/basic/fixdep.c. Missing symbol files also correspond
+        to "not changed", which fixdep deals with by using the $(wildcard) Make
+        function when adding symbol prerequisites to source files.
+
+        In case you need a different scheme for your project, the sync_deps()
+        implementation can be used as a template.
+        """
+        if not exists(path):
+            os.mkdir(path, 0o755)
+
+        # Load old values from auto.conf, if any
+        self._load_old_vals(path)
+
+        for sym in self.unique_defined_syms:
+            # _write_to_conf is determined when the value is calculated. This
+            # is a hidden function call due to property magic.
+            #
+            # Note: In client code, you can check if sym.config_string is empty
+            # instead, to avoid accessing the internal _write_to_conf variable
+            # (though it's likely to keep working).
+            val = sym.str_value
+
+            # n tristate values do not get written to auto.conf and autoconf.h,
+            # making a missing symbol logically equivalent to n
+
+            if sym._write_to_conf:
+                if sym._old_val is None and \
+                   sym.orig_type in _BOOL_TRISTATE and \
+                   val == "n":
+                    # No old value (the symbol was missing or n), new value n.
+                    # No change.
+                    continue
+
+                if val == sym._old_val:
+                    # New value matches old. No change.
+                    continue
+
+            elif sym._old_val is None:
+                # The symbol wouldn't appear in autoconf.h (because
+                # _write_to_conf is false), and it wouldn't have appeared in
+                # autoconf.h previously either (because it didn't appear in
+                # auto.conf). No change.
+                continue
+
+            # 'sym' has a new value. Flag it.
+            _touch_dep_file(path, sym.name)
+
+        # Remember the current values as the "new old" values.
+        #
+        # This call could go anywhere after the call to _load_old_vals(), but
+        # putting it last means _sync_deps() can be safely rerun if it fails
+        # before this point.
+        self._write_old_vals(path)
+
+    def _load_old_vals(self, path):
+        # Loads old symbol values from auto.conf into a dedicated
+        # Symbol._old_val field. Mirrors load_config().
+        #
+        # The extra field could be avoided with some trickery involving dumping
+        # symbol values and restoring them later, but this is simpler and
+        # faster. The C tools also use a dedicated field for this purpose.
+
+        for sym in self.unique_defined_syms:
+            sym._old_val = None
+
+        try:
+            auto_conf = self._open(join(path, "auto.conf"), "r")
+        except EnvironmentError as e:
+            if e.errno == errno.ENOENT:
+                # No old values
+                return
+            raise
+
+        with auto_conf as f:
+            for line in f:
+                match = self._set_match(line)
+                if not match:
+                    # We only expect CONFIG_FOO=... (and possibly a header
+                    # comment) in auto.conf
+                    continue
+
+                name, val = match.groups()
+                if name in self.syms:
+                    sym = self.syms[name]
+
+                    if sym.orig_type is STRING:
+                        match = _conf_string_match(val)
+                        if not match:
+                            continue
+                        val = unescape(match.group(1))
+
+                    self.syms[name]._old_val = val
+                else:
+                    # Flag that the symbol no longer exists, in
+                    # case something still depends on it
+                    _touch_dep_file(path, name)
+
+    def _write_old_vals(self, path):
+        # Helper for writing auto.conf. Basically just a simplified
+        # write_config() that doesn't write any comments (including
+        # '# CONFIG_FOO is not set' comments). The format matches the C
+        # implementation, though the ordering is arbitrary there (depends on
+        # the hash table implementation).
+        #
+        # A separate helper function is neater than complicating write_config()
+        # by passing a flag to it, plus we only need to look at symbols here.
+
+        self._write_if_changed(
+            os.path.join(path, "auto.conf"),
+            self._old_vals_contents())
+
+    def _old_vals_contents(self):
+        # _write_old_vals() helper. Returns the contents to write as a string.
+
+        # Temporary list instead of generator makes this a bit faster
+        return "".join([
+            sym.config_string for sym in self.unique_defined_syms
+                if not (sym.orig_type in _BOOL_TRISTATE and not sym.tri_value)
+        ])
+
+    def node_iter(self, unique_syms=False):
+        """
+        Returns a generator for iterating through all MenuNode's in the Kconfig
+        tree. The iteration is done in Kconfig definition order (each node is
+        visited before its children, and the children of a node are visited
+        before the next node).
+
+        The Kconfig.top_node menu node is skipped. It contains an implicit menu
+        that holds the top-level items.
+
+        As an example, the following code will produce a list equal to
+        Kconfig.defined_syms:
+
+          defined_syms = [node.item for node in kconf.node_iter()
+                          if isinstance(node.item, Symbol)]
+
+        unique_syms (default: False):
+          If True, only the first MenuNode will be included for symbols defined
+          in multiple locations.
+
+          Using kconf.node_iter(True) in the example above would give a list
+          equal to unique_defined_syms.
+        """
+        if unique_syms:
+            for sym in self.unique_defined_syms:
+                sym._visited = False
+
+        node = self.top_node
+        while 1:
+            # Jump to the next node with an iterative tree walk
+            if node.list:
+                node = node.list
+            elif node.next:
+                node = node.next
+            else:
+                while node.parent:
+                    node = node.parent
+                    if node.next:
+                        node = node.next
+                        break
+                else:
+                    # No more nodes
+                    return
+
+            if unique_syms and node.item.__class__ is Symbol:
+                if node.item._visited:
+                    continue
+                node.item._visited = True
+
+            yield node
+
+    def eval_string(self, s):
+        """
+        Returns the tristate value of the expression 's', represented as 0, 1,
+        and 2 for n, m, and y, respectively. Raises KconfigError on syntax
+        errors. Warns if undefined symbols are referenced.
+
+        As an example, if FOO and BAR are tristate symbols at least one of
+        which has the value y, then eval_string("y && (FOO || BAR)") returns
+        2 (y).
+
+        To get the string value of non-bool/tristate symbols, use
+        Symbol.str_value. eval_string() always returns a tristate value, and
+        all non-bool/tristate symbols have the tristate value 0 (n).
+
+        The expression parsing is consistent with how parsing works for
+        conditional ('if ...') expressions in the configuration, and matches
+        the C implementation. m is rewritten to 'm && MODULES', so
+        eval_string("m") will return 0 (n) unless modules are enabled.
+        """
+        # The parser is optimized to be fast when parsing Kconfig files (where
+        # an expression can never appear at the beginning of a line). We have
+        # to monkey-patch things a bit here to reuse it.
+
+        self.filename = None
+
+        self._tokens = self._tokenize("if " + s)
+        # Strip "if " to avoid giving confusing error messages
+        self._line = s
+        self._tokens_i = 1  # Skip the 'if' token
+
+        return expr_value(self._expect_expr_and_eol())
+
+    def unset_values(self):
+        """
+        Removes any user values from all symbols, as if Kconfig.load_config()
+        or Symbol.set_value() had never been called.
+        """
+        self._warn_assign_no_prompt = False
+        try:
+            # set_value() already rejects undefined symbols, and they don't
+            # need to be invalidated (because their value never changes), so we
+            # can just iterate over defined symbols
+            for sym in self.unique_defined_syms:
+                sym.unset_value()
+
+            for choice in self.unique_choices:
+                choice.unset_value()
+        finally:
+            self._warn_assign_no_prompt = True
+
+    def enable_warnings(self):
+        """
+        Do 'Kconfig.warn = True' instead. Maintained for backwards
+        compatibility.
+        """
+        self.warn = True
+
+    def disable_warnings(self):
+        """
+        Do 'Kconfig.warn = False' instead. Maintained for backwards
+        compatibility.
+        """
+        self.warn = False
+
+    def enable_stderr_warnings(self):
+        """
+        Do 'Kconfig.warn_to_stderr = True' instead. Maintained for backwards
+        compatibility.
+        """
+        self.warn_to_stderr = True
+
+    def disable_stderr_warnings(self):
+        """
+        Do 'Kconfig.warn_to_stderr = False' instead. Maintained for backwards
+        compatibility.
+        """
+        self.warn_to_stderr = False
+
+    def enable_undef_warnings(self):
+        """
+        Do 'Kconfig.warn_assign_undef = True' instead. Maintained for backwards
+        compatibility.
+        """
+        self.warn_assign_undef = True
+
+    def disable_undef_warnings(self):
+        """
+        Do 'Kconfig.warn_assign_undef = False' instead. Maintained for
+        backwards compatibility.
+        """
+        self.warn_assign_undef = False
+
+    def enable_override_warnings(self):
+        """
+        Do 'Kconfig.warn_assign_override = True' instead. Maintained for
+        backwards compatibility.
+        """
+        self.warn_assign_override = True
+
+    def disable_override_warnings(self):
+        """
+        Do 'Kconfig.warn_assign_override = False' instead. Maintained for
+        backwards compatibility.
+        """
+        self.warn_assign_override = False
+
+    def enable_redun_warnings(self):
+        """
+        Do 'Kconfig.warn_assign_redun = True' instead. Maintained for backwards
+        compatibility.
+        """
+        self.warn_assign_redun = True
+
+    def disable_redun_warnings(self):
+        """
+        Do 'Kconfig.warn_assign_redun = False' instead. Maintained for
+        backwards compatibility.
+        """
+        self.warn_assign_redun = False
+
+    def __repr__(self):
+        """
+        Returns a string with information about the Kconfig object when it is
+        evaluated on e.g. the interactive Python prompt.
+        """
+        def status(flag):
+            return "enabled" if flag else "disabled"
+
+        return "<{}>".format(", ".join((
+            "configuration with {} symbols".format(len(self.syms)),
+            'main menu prompt "{}"'.format(self.mainmenu_text),
+            "srctree is current directory" if not self.srctree else
+                'srctree "{}"'.format(self.srctree),
+            'config symbol prefix "{}"'.format(self.config_prefix),
+            "warnings " + status(self.warn),
+            "printing of warnings to stderr " + status(self.warn_to_stderr),
+            "undef. symbol assignment warnings " +
+                status(self.warn_assign_undef),
+            "overriding symbol assignment warnings " +
+                status(self.warn_assign_override),
+            "redundant symbol assignment warnings " +
+                status(self.warn_assign_redun)
+        )))
+
+    #
+    # Private methods
+    #
+
+
+    #
+    # File reading
+    #
+
+    def _open_config(self, filename):
+        # Opens a .config file. First tries to open 'filename', then
+        # '$srctree/filename' if $srctree was set when the configuration was
+        # loaded.
+
+        try:
+            return self._open(filename, "r")
+        except EnvironmentError as e:
+            # This will try opening the same file twice if $srctree is unset,
+            # but it's not a big deal
+            try:
+                return self._open(join(self.srctree, filename), "r")
+            except EnvironmentError as e2:
+                # This is needed for Python 3, because e2 is deleted after
+                # the try block:
+                #
+                # https://docs.python.org/3/reference/compound_stmts.html#the-try-statement
+                e = e2
+
+            raise _KconfigIOError(
+                e, "Could not open '{}' ({}: {}). Check that the $srctree "
+                   "environment variable ({}) is set correctly."
+                   .format(filename, errno.errorcode[e.errno], e.strerror,
+                           "set to '{}'".format(self.srctree) if self.srctree
+                               else "unset or blank"))
+
+    def _enter_file(self, filename):
+        # Jumps to the beginning of a sourced Kconfig file, saving the previous
+        # position and file object.
+        #
+        # filename:
+        #   Absolute path to file
+
+        # Path relative to $srctree, stored in e.g. self.filename (which makes
+        # it indirectly show up in MenuNode.filename). Equals 'filename' for
+        # absolute paths passed to 'source'.
+        if filename.startswith(self._srctree_prefix):
+            # Relative path (or a redundant absolute path to within $srctree,
+            # but it's probably fine to reduce those too)
+            rel_filename = filename[len(self._srctree_prefix):]
+        else:
+            # Absolute path
+            rel_filename = filename
+
+        self.kconfig_filenames.append(rel_filename)
+
+        # The parent Kconfig files are represented as a list of
+        # (<include path>, <Python 'file' object for Kconfig file>) tuples.
+        #
+        # <include path> is immutable and holds a *tuple* of
+        # (<filename>, <linenr>) tuples, giving the locations of the 'source'
+        # statements in the parent Kconfig files. The current include path is
+        # also available in Kconfig._include_path.
+        #
+        # The point of this redundant setup is to allow Kconfig._include_path
+        # to be assigned directly to MenuNode.include_path without having to
+        # copy it, sharing it wherever possible.
+
+        # Save include path and 'file' object (via its 'readline' function)
+        # before entering the file
+        self._filestack.append((self._include_path, self._readline))
+
+        # _include_path is a tuple, so this rebinds the variable instead of
+        # doing in-place modification
+        self._include_path += ((self.filename, self.linenr),)
+
+        # Check for recursive 'source'
+        for name, _ in self._include_path:
+            if name == rel_filename:
+                raise KconfigError(
+                    "\n{}:{}: recursive 'source' of '{}' detected. Check that "
+                    "environment variables are set correctly.\n"
+                    "Include path:\n{}"
+                    .format(self.filename, self.linenr, rel_filename,
+                            "\n".join("{}:{}".format(name, linenr)
+                                      for name, linenr in self._include_path)))
+
+        try:
+            self._readline = self._open(filename, "r").readline
+        except EnvironmentError as e:
+            # We already know that the file exists
+            raise _KconfigIOError(
+                e, "{}:{}: Could not open '{}' (in '{}') ({}: {})"
+                   .format(self.filename, self.linenr, filename,
+                           self._line.strip(),
+                           errno.errorcode[e.errno], e.strerror))
+
+        self.filename = rel_filename
+        self.linenr = 0
+
+    def _leave_file(self):
+        # Returns from a Kconfig file to the file that sourced it. See
+        # _enter_file().
+
+        # Restore location from parent Kconfig file
+        self.filename, self.linenr = self._include_path[-1]
+        # Restore include path and 'file' object
+        self._readline.__self__.close()  # __self__ fetches the 'file' object
+        self._include_path, self._readline = self._filestack.pop()
+
+    def _next_line(self):
+        # Fetches and tokenizes the next line from the current Kconfig file.
+        # Returns False at EOF and True otherwise.
+
+        # We might already have tokens from parsing a line and discovering that
+        # it's part of a different construct
+        if self._reuse_tokens:
+            self._reuse_tokens = False
+            # self._tokens_i is known to be 1 here, because _parse_props()
+            # leaves it like that when it can't recognize a line (or parses a
+            # help text)
+            return True
+
+        # readline() returns '' over and over at EOF, which we rely on for help
+        # texts at the end of files (see _line_after_help())
+        line = self._readline()
+        if not line:
+            return False
+        self.linenr += 1
+
+        # Handle line joining
+        while line.endswith("\\\n"):
+            line = line[:-2] + self._readline()
+            self.linenr += 1
+
+        self._tokens = self._tokenize(line)
+        # Initialize to 1 instead of 0 to factor out code from _parse_block()
+        # and _parse_props(). They immediately fetch self._tokens[0].
+        self._tokens_i = 1
+
+        return True
+
+    def _line_after_help(self, line):
+        # Tokenizes a line after a help text. This case is special in that the
+        # line has already been fetched (to discover that it isn't part of the
+        # help text).
+        #
+        # An earlier version used a _saved_line variable instead that was
+        # checked in _next_line(). This special-casing gets rid of it and makes
+        # _reuse_tokens alone sufficient to handle unget.
+
+        # Handle line joining
+        while line.endswith("\\\n"):
+            line = line[:-2] + self._readline()
+            self.linenr += 1
+
+        self._tokens = self._tokenize(line)
+        self._reuse_tokens = True
+
+    def _write_if_changed(self, filename, contents):
+        # Writes 'contents' into 'filename', but only if it differs from the
+        # current contents of the file.
+        #
+        # Another variant would be write a temporary file on the same
+        # filesystem, compare the files, and rename() the temporary file if it
+        # differs, but it breaks stuff like write_config("/dev/null"), which is
+        # used out there to force evaluation-related warnings to be generated.
+        # This simple version is pretty failsafe and portable.
+        #
+        # Returns True if the file has changed and is updated, and False
+        # otherwise.
+
+        if self._contents_eq(filename, contents):
+            return False
+        with self._open(filename, "w") as f:
+            f.write(contents)
+        return True
+
+    def _contents_eq(self, filename, contents):
+        # Returns True if the contents of 'filename' is 'contents' (a string),
+        # and False otherwise (including if 'filename' can't be opened/read)
+
+        try:
+            with self._open(filename, "r") as f:
+                # Robust re. things like encoding and line endings (mmap()
+                # trickery isn't)
+                return f.read(len(contents) + 1) == contents
+        except EnvironmentError:
+            # If the error here would prevent writing the file as well, we'll
+            # notice it later
+            return False
+
+    #
+    # Tokenization
+    #
+
+    def _lookup_sym(self, name):
+        # Fetches the symbol 'name' from the symbol table, creating and
+        # registering it if it does not exist. If '_parsing_kconfigs' is False,
+        # it means we're in eval_string(), and new symbols won't be registered.
+
+        if name in self.syms:
+            return self.syms[name]
+
+        sym = Symbol()
+        sym.kconfig = self
+        sym.name = name
+        sym.is_constant = False
+        sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+        if self._parsing_kconfigs:
+            self.syms[name] = sym
+        else:
+            self._warn("no symbol {} in configuration".format(name))
+
+        return sym
+
+    def _lookup_const_sym(self, name):
+        # Like _lookup_sym(), for constant (quoted) symbols
+
+        if name in self.const_syms:
+            return self.const_syms[name]
+
+        sym = Symbol()
+        sym.kconfig = self
+        sym.name = name
+        sym.is_constant = True
+        sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+        if self._parsing_kconfigs:
+            self.const_syms[name] = sym
+
+        return sym
+
+    def _tokenize(self, s):
+        # Parses 's', returning a None-terminated list of tokens. Registers any
+        # new symbols encountered with _lookup(_const)_sym().
+        #
+        # Tries to be reasonably speedy by processing chunks of text via
+        # regexes and string operations where possible. This is the biggest
+        # hotspot during parsing.
+        #
+        # It might be possible to rewrite this to 'yield' tokens instead,
+        # working across multiple lines. Lookback and compatibility with old
+        # janky versions of the C tools complicate things though.
+
+        self._line = s  # Used for error reporting
+
+        # Initial token on the line
+        match = _command_match(s)
+        if not match:
+            if s.isspace() or s.lstrip().startswith("#"):
+                return (None,)
+            self._parse_error("unknown token at start of line")
+
+        # Tricky implementation detail: While parsing a token, 'token' refers
+        # to the previous token. See _STRING_LEX for why this is needed.
+        token = _get_keyword(match.group(1))
+        if not token:
+            # Backwards compatibility with old versions of the C tools, which
+            # (accidentally) accepted stuff like "--help--" and "-help---".
+            # This was fixed in the C tools by commit c2264564 ("kconfig: warn
+            # of unhandled characters in Kconfig commands"), committed in July
+            # 2015, but it seems people still run Kconfiglib on older kernels.
+            if s.strip(" \t\n-") == "help":
+                return (_T_HELP, None)
+
+            # If the first token is not a keyword (and not a weird help token),
+            # we have a preprocessor variable assignment (or a bare macro on a
+            # line)
+            self._parse_assignment(s)
+            return (None,)
+
+        tokens = [token]
+        # The current index in the string being tokenized
+        i = match.end()
+
+        # Main tokenization loop (for tokens past the first one)
+        while i < len(s):
+            # Test for an identifier/keyword first. This is the most common
+            # case.
+            match = _id_keyword_match(s, i)
+            if match:
+                # We have an identifier or keyword
+
+                # Check what it is. lookup_sym() will take care of allocating
+                # new symbols for us the first time we see them. Note that
+                # 'token' still refers to the previous token.
+
+                name = match.group(1)
+                keyword = _get_keyword(name)
+                if keyword:
+                    # It's a keyword
+                    token = keyword
+                    # Jump past it
+                    i = match.end()
+
+                elif token not in _STRING_LEX:
+                    # It's a non-const symbol, except we translate n, m, and y
+                    # into the corresponding constant symbols, like the C
+                    # implementation
+
+                    if "$" in name:
+                        # Macro expansion within symbol name
+                        name, s, i = self._expand_name(s, i)
+                    else:
+                        i = match.end()
+
+                    token = self.const_syms[name] if name in STR_TO_TRI else \
+                        self._lookup_sym(name)
+
+                else:
+                    # It's a case of missing quotes. For example, the
+                    # following is accepted:
+                    #
+                    #   menu unquoted_title
+                    #
+                    #   config A
+                    #       tristate unquoted_prompt
+                    #
+                    #   endmenu
+                    #
+                    # Named choices ('choice FOO') also end up here.
+
+                    if token is not _T_CHOICE:
+                        self._warn("style: quotes recommended around '{}' in '{}'"
+                                   .format(name, self._line.strip()),
+                                   self.filename, self.linenr)
+
+                    token = name
+                    i = match.end()
+
+            else:
+                # Neither a keyword nor a non-const symbol
+
+                # We always strip whitespace after tokens, so it is safe to
+                # assume that s[i] is the start of a token here.
+                c = s[i]
+
+                if c in "\"'":
+                    if "$" not in s and "\\" not in s:
+                        # Fast path for lines without $ and \. Find the
+                        # matching quote.
+                        end_i = s.find(c, i + 1) + 1
+                        if not end_i:
+                            self._parse_error("unterminated string")
+                        val = s[i + 1:end_i - 1]
+                        i = end_i
+                    else:
+                        # Slow path
+                        s, end_i = self._expand_str(s, i)
+
+                        # os.path.expandvars() and the $UNAME_RELEASE replace()
+                        # is a backwards compatibility hack, which should be
+                        # reasonably safe as expandvars() leaves references to
+                        # undefined env. vars. as is.
+                        #
+                        # The preprocessor functionality changed how
+                        # environment variables are referenced, to $(FOO).
+                        val = expandvars(s[i + 1:end_i - 1]
+                                         .replace("$UNAME_RELEASE",
+                                                  _UNAME_RELEASE))
+
+                        i = end_i
+
+                    # This is the only place where we don't survive with a
+                    # single token of lookback: 'option env="FOO"' does not
+                    # refer to a constant symbol named "FOO".
+                    token = \
+                        val if token in _STRING_LEX or tokens[0] is _T_OPTION \
+                        else self._lookup_const_sym(val)
+
+                elif s.startswith("&&", i):
+                    token = _T_AND
+                    i += 2
+
+                elif s.startswith("||", i):
+                    token = _T_OR
+                    i += 2
+
+                elif c == "=":
+                    token = _T_EQUAL
+                    i += 1
+
+                elif s.startswith("!=", i):
+                    token = _T_UNEQUAL
+                    i += 2
+
+                elif c == "!":
+                    token = _T_NOT
+                    i += 1
+
+                elif c == "(":
+                    token = _T_OPEN_PAREN
+                    i += 1
+
+                elif c == ")":
+                    token = _T_CLOSE_PAREN
+                    i += 1
+
+                elif c == "#":
+                    break
+
+
+                # Very rare
+
+                elif s.startswith("<=", i):
+                    token = _T_LESS_EQUAL
+                    i += 2
+
+                elif c == "<":
+                    token = _T_LESS
+                    i += 1
+
+                elif s.startswith(">=", i):
+                    token = _T_GREATER_EQUAL
+                    i += 2
+
+                elif c == ">":
+                    token = _T_GREATER
+                    i += 1
+
+
+                else:
+                    self._parse_error("unknown tokens in line")
+
+
+                # Skip trailing whitespace
+                while i < len(s) and s[i].isspace():
+                    i += 1
+
+
+            # Add the token
+            tokens.append(token)
+
+        # None-terminating the token list makes token fetching simpler/faster
+        tokens.append(None)
+
+        return tokens
+
+    # Helpers for syntax checking and token fetching. See the
+    # 'Intro to expressions' section for what a constant symbol is.
+    #
+    # More of these could be added, but the single-use cases are inlined as an
+    # optimization.
+
+    def _expect_sym(self):
+        token = self._tokens[self._tokens_i]
+        self._tokens_i += 1
+
+        if token.__class__ is not Symbol:
+            self._parse_error("expected symbol")
+
+        return token
+
+    def _expect_nonconst_sym(self):
+        # Used for 'select' and 'imply' only. We know the token indices.
+
+        token = self._tokens[1]
+        self._tokens_i = 2
+
+        if token.__class__ is not Symbol or token.is_constant:
+            self._parse_error("expected nonconstant symbol")
+
+        return token
+
+    def _expect_str_and_eol(self):
+        token = self._tokens[self._tokens_i]
+        self._tokens_i += 1
+
+        if token.__class__ is not str:
+            self._parse_error("expected string")
+
+        if self._tokens[self._tokens_i] is not None:
+            self._trailing_tokens_error()
+
+        return token
+
+    def _expect_expr_and_eol(self):
+        expr = self._parse_expr(True)
+
+        if self._tokens[self._tokens_i] is not None:
+            self._trailing_tokens_error()
+
+        return expr
+
+    def _check_token(self, token):
+        # If the next token is 'token', removes it and returns True
+
+        if self._tokens[self._tokens_i] is token:
+            self._tokens_i += 1
+            return True
+        return False
+
+    #
+    # Preprocessor logic
+    #
+
+    def _parse_assignment(self, s):
+        # Parses a preprocessor variable assignment, registering the variable
+        # if it doesn't already exist. Also takes care of bare macros on lines
+        # (which are allowed, and can be useful for their side effects).
+
+        # Expand any macros in the left-hand side of the assignment (the
+        # variable name)
+        s = s.lstrip()
+        i = 0
+        while 1:
+            i = _assignment_lhs_fragment_match(s, i).end()
+            if s.startswith("$(", i):
+                s, i = self._expand_macro(s, i, ())
+            else:
+                break
+
+        if s.isspace():
+            # We also accept a bare macro on a line (e.g.
+            # $(warning-if,$(foo),ops)), provided it expands to a blank string
+            return
+
+        # Assigned variable
+        name = s[:i]
+
+
+        # Extract assignment operator (=, :=, or +=) and value
+        rhs_match = _assignment_rhs_match(s, i)
+        if not rhs_match:
+            self._parse_error("syntax error")
+
+        op, val = rhs_match.groups()
+
+
+        if name in self.variables:
+            # Already seen variable
+            var = self.variables[name]
+        else:
+            # New variable
+            var = Variable()
+            var.kconfig = self
+            var.name = name
+            var._n_expansions = 0
+            self.variables[name] = var
+
+            # += acts like = on undefined variables (defines a recursive
+            # variable)
+            if op == "+=":
+                op = "="
+
+        if op == "=":
+            var.is_recursive = True
+            var.value = val
+        elif op == ":=":
+            var.is_recursive = False
+            var.value = self._expand_whole(val, ())
+        else:  # op == "+="
+            # += does immediate expansion if the variable was last set
+            # with :=
+            var.value += " " + (val if var.is_recursive else
+                                self._expand_whole(val, ()))
+
+    def _expand_whole(self, s, args):
+        # Expands preprocessor macros in all of 's'. Used whenever we don't
+        # have to worry about delimiters. See _expand_macro() re. the 'args'
+        # parameter.
+        #
+        # Returns the expanded string.
+
+        i = 0
+        while 1:
+            i = s.find("$(", i)
+            if i == -1:
+                break
+            s, i = self._expand_macro(s, i, args)
+        return s
+
+    def _expand_name(self, s, i):
+        # Expands a symbol name starting at index 'i' in 's'.
+        #
+        # Returns the expanded name, the expanded 's' (including the part
+        # before the name), and the index of the first character in the next
+        # token after the name.
+
+        s, end_i = self._expand_name_iter(s, i)
+        name = s[i:end_i]
+        # isspace() is False for empty strings
+        if not name.strip():
+            # Avoid creating a Kconfig symbol with a blank name. It's almost
+            # guaranteed to be an error.
+            self._parse_error("macro expanded to blank string")
+
+        # Skip trailing whitespace
+        while end_i < len(s) and s[end_i].isspace():
+            end_i += 1
+
+        return name, s, end_i
+
+    def _expand_name_iter(self, s, i):
+        # Expands a symbol name starting at index 'i' in 's'.
+        #
+        # Returns the expanded 's' (including the part before the name) and the
+        # index of the first character after the expanded name in 's'.
+
+        while 1:
+            match = _name_special_search(s, i)
+
+            if match.group() != "$(":
+                return (s, match.start())
+            s, i = self._expand_macro(s, match.start(), ())
+
+    def _expand_str(self, s, i):
+        # Expands a quoted string starting at index 'i' in 's'. Handles both
+        # backslash escapes and macro expansion.
+        #
+        # Returns the expanded 's' (including the part before the string) and
+        # the index of the first character after the expanded string in 's'.
+
+        quote = s[i]
+        i += 1  # Skip over initial "/'
+        while 1:
+            match = _string_special_search(s, i)
+            if not match:
+                self._parse_error("unterminated string")
+
+
+            if match.group() == quote:
+                # Found the end of the string
+                return (s, match.end())
+
+            elif match.group() == "\\":
+                # Replace '\x' with 'x'. 'i' ends up pointing to the character
+                # after 'x', which allows macros to be canceled with '\$(foo)'.
+                i = match.end()
+                s = s[:match.start()] + s[i:]
+
+            elif match.group() == "$(":
+                # A macro call within the string
+                s, i = self._expand_macro(s, match.start(), ())
+
+            else:
+                # A ' quote within " quotes or vice versa
+                i += 1
+
+    def _expand_macro(self, s, i, args):
+        # Expands a macro starting at index 'i' in 's'. If this macro resulted
+        # from the expansion of another macro, 'args' holds the arguments
+        # passed to that macro.
+        #
+        # Returns the expanded 's' (including the part before the macro) and
+        # the index of the first character after the expanded macro in 's'.
+
+        res = s[:i]
+        i += 2  # Skip over "$("
+
+        arg_start = i  # Start of current macro argument
+        new_args = []  # Arguments of this macro call
+        nesting = 0  # Current parentheses nesting level
+
+        while 1:
+            match = _macro_special_search(s, i)
+            if not match:
+                self._parse_error("missing end parenthesis in macro expansion")
+
+
+            if match.group() == "(":
+                nesting += 1
+                i = match.end()
+
+            elif match.group() == ")":
+                if nesting:
+                    nesting -= 1
+                    i = match.end()
+                    continue
+
+                # Found the end of the macro
+
+                new_args.append(s[arg_start:match.start()])
+
+                # $(1) is replaced by the first argument to the function, etc.,
+                # provided at least that many arguments were passed
+
+                try:
+                    # Does the macro look like an integer, with a corresponding
+                    # argument? If so, expand it to the value of the argument.
+                    res += args[int(new_args[0])]
+                except (ValueError, IndexError):
+                    # Regular variables are just functions without arguments,
+                    # and also go through the function value path
+                    res += self._fn_val(new_args)
+
+                return (res + s[match.end():], len(res))
+
+            elif match.group() == ",":
+                i = match.end()
+                if nesting:
+                    continue
+
+                # Found the end of a macro argument
+                new_args.append(s[arg_start:match.start()])
+                arg_start = i
+
+            else:  # match.group() == "$("
+                # A nested macro call within the macro
+                s, i = self._expand_macro(s, match.start(), args)
+
+    def _fn_val(self, args):
+        # Returns the result of calling the function args[0] with the arguments
+        # args[1..len(args)-1]. Plain variables are treated as functions
+        # without arguments.
+
+        fn = args[0]
+
+        if fn in self.variables:
+            var = self.variables[fn]
+
+            if len(args) == 1:
+                # Plain variable
+                if var._n_expansions:
+                    self._parse_error("Preprocessor variable {} recursively "
+                                      "references itself".format(var.name))
+            elif var._n_expansions > 100:
+                # Allow functions to call themselves, but guess that functions
+                # that are overly recursive are stuck
+                self._parse_error("Preprocessor function {} seems stuck "
+                                  "in infinite recursion".format(var.name))
+
+            var._n_expansions += 1
+            res = self._expand_whole(self.variables[fn].value, args)
+            var._n_expansions -= 1
+            return res
+
+        if fn in self._functions:
+            # Built-in or user-defined function
+
+            py_fn, min_arg, max_arg = self._functions[fn]
+
+            if len(args) - 1 < min_arg or \
+               (max_arg is not None and len(args) - 1 > max_arg):
+
+                if min_arg == max_arg:
+                    expected_args = min_arg
+                elif max_arg is None:
+                    expected_args = "{} or more".format(min_arg)
+                else:
+                    expected_args = "{}-{}".format(min_arg, max_arg)
+
+                raise KconfigError("{}:{}: bad number of arguments in call "
+                                   "to {}, expected {}, got {}"
+                                   .format(self.filename, self.linenr, fn,
+                                           expected_args, len(args) - 1))
+
+            return py_fn(self, *args)
+
+        # Environment variables are tried last
+        if fn in os.environ:
+            self.env_vars.add(fn)
+            return os.environ[fn]
+
+        return ""
+
+    #
+    # Parsing
+    #
+
+    def _make_and(self, e1, e2):
+        # Constructs an AND (&&) expression. Performs trivial simplification.
+
+        if e1 is self.y:
+            return e2
+
+        if e2 is self.y:
+            return e1
+
+        if e1 is self.n or e2 is self.n:
+            return self.n
+
+        return (AND, e1, e2)
+
+    def _make_or(self, e1, e2):
+        # Constructs an OR (||) expression. Performs trivial simplification.
+
+        if e1 is self.n:
+            return e2
+
+        if e2 is self.n:
+            return e1
+
+        if e1 is self.y or e2 is self.y:
+            return self.y
+
+        return (OR, e1, e2)
+
+    def _parse_block(self, end_token, parent, prev):
+        # Parses a block, which is the contents of either a file or an if,
+        # menu, or choice statement.
+        #
+        # end_token:
+        #   The token that ends the block, e.g. _T_ENDIF ("endif") for ifs.
+        #   None for files.
+        #
+        # parent:
+        #   The parent menu node, corresponding to a menu, Choice, or 'if'.
+        #   'if's are flattened after parsing.
+        #
+        # prev:
+        #   The previous menu node. New nodes will be added after this one (by
+        #   modifying 'next' pointers).
+        #
+        #   'prev' is reused to parse a list of child menu nodes (for a menu or
+        #   Choice): After parsing the children, the 'next' pointer is assigned
+        #   to the 'list' pointer to "tilt up" the children above the node.
+        #
+        # Returns the final menu node in the block (or 'prev' if the block is
+        # empty). This allows chaining.
+
+        while self._next_line():
+            t0 = self._tokens[0]
+
+            if t0 is _T_CONFIG or t0 is _T_MENUCONFIG:
+                # The tokenizer allocates Symbol objects for us
+                sym = self._tokens[1]
+
+                if sym.__class__ is not Symbol or sym.is_constant:
+                    self._parse_error("missing or bad symbol name")
+
+                if self._tokens[2] is not None:
+                    self._trailing_tokens_error()
+
+                self.defined_syms.append(sym)
+
+                node = MenuNode()
+                node.kconfig = self
+                node.item = sym
+                node.is_menuconfig = (t0 is _T_MENUCONFIG)
+                node.prompt = node.help = node.list = None
+                node.parent = parent
+                node.filename = self.filename
+                node.linenr = self.linenr
+                node.include_path = self._include_path
+
+                sym.nodes.append(node)
+
+                self._parse_props(node)
+
+                if node.is_menuconfig and not node.prompt:
+                    self._warn("the menuconfig symbol {} has no prompt"
+                               .format(sym.name_and_loc))
+
+                # Equivalent to
+                #
+                #   prev.next = node
+                #   prev = node
+                #
+                # due to tricky Python semantics. The order matters.
+                prev.next = prev = node
+
+            elif t0 is None:
+                # Blank line
+                continue
+
+            elif t0 in _SOURCE_TOKENS:
+                pattern = self._expect_str_and_eol()
+
+                if t0 in _REL_SOURCE_TOKENS:
+                    # Relative source
+                    pattern = join(dirname(self.filename), pattern)
+
+                # - glob() doesn't support globbing relative to a directory, so
+                #   we need to prepend $srctree to 'pattern'. Use join()
+                #   instead of '+' so that an absolute path in 'pattern' is
+                #   preserved.
+                #
+                # - Sort the glob results to ensure a consistent ordering of
+                #   Kconfig symbols, which indirectly ensures a consistent
+                #   ordering in e.g. .config files
+                filenames = sorted(iglob(join(self._srctree_prefix, pattern)))
+
+                if not filenames and t0 in _OBL_SOURCE_TOKENS:
+                    raise KconfigError(
+                        "{}:{}: '{}' not found (in '{}'). Check that "
+                        "environment variables are set correctly (e.g. "
+                        "$srctree, which is {}). Also note that unset "
+                        "environment variables expand to the empty string."
+                        .format(self.filename, self.linenr, pattern,
+                                self._line.strip(),
+                                "set to '{}'".format(self.srctree)
+                                    if self.srctree else "unset or blank"))
+
+                for filename in filenames:
+                    self._enter_file(filename)
+                    prev = self._parse_block(None, parent, prev)
+                    self._leave_file()
+
+            elif t0 is end_token:
+                # Reached the end of the block. Terminate the final node and
+                # return it.
+
+                if self._tokens[1] is not None:
+                    self._trailing_tokens_error()
+
+                prev.next = None
+                return prev
+
+            elif t0 is _T_IF:
+                node = MenuNode()
+                node.item = node.prompt = None
+                node.parent = parent
+                node.dep = self._expect_expr_and_eol()
+
+                self._parse_block(_T_ENDIF, node, node)
+                node.list = node.next
+
+                prev.next = prev = node
+
+            elif t0 is _T_MENU:
+                node = MenuNode()
+                node.kconfig = self
+                node.item = t0  # _T_MENU == MENU
+                node.is_menuconfig = True
+                node.prompt = (self._expect_str_and_eol(), self.y)
+                node.visibility = self.y
+                node.parent = parent
+                node.filename = self.filename
+                node.linenr = self.linenr
+                node.include_path = self._include_path
+
+                self.menus.append(node)
+
+                self._parse_props(node)
+                self._parse_block(_T_ENDMENU, node, node)
+                node.list = node.next
+
+                prev.next = prev = node
+
+            elif t0 is _T_COMMENT:
+                node = MenuNode()
+                node.kconfig = self
+                node.item = t0  # _T_COMMENT == COMMENT
+                node.is_menuconfig = False
+                node.prompt = (self._expect_str_and_eol(), self.y)
+                node.list = None
+                node.parent = parent
+                node.filename = self.filename
+                node.linenr = self.linenr
+                node.include_path = self._include_path
+
+                self.comments.append(node)
+
+                self._parse_props(node)
+
+                prev.next = prev = node
+
+            elif t0 is _T_CHOICE:
+                if self._tokens[1] is None:
+                    choice = Choice()
+                    choice.direct_dep = self.n
+                else:
+                    # Named choice
+                    name = self._expect_str_and_eol()
+                    choice = self.named_choices.get(name)
+                    if not choice:
+                        choice = Choice()
+                        choice.name = name
+                        choice.direct_dep = self.n
+                        self.named_choices[name] = choice
+
+                self.choices.append(choice)
+
+                node = MenuNode()
+                node.kconfig = choice.kconfig = self
+                node.item = choice
+                node.is_menuconfig = True
+                node.prompt = node.help = None
+                node.parent = parent
+                node.filename = self.filename
+                node.linenr = self.linenr
+                node.include_path = self._include_path
+
+                choice.nodes.append(node)
+
+                self._parse_props(node)
+                self._parse_block(_T_ENDCHOICE, node, node)
+                node.list = node.next
+
+                prev.next = prev = node
+
+            elif t0 is _T_MAINMENU:
+                self.top_node.prompt = (self._expect_str_and_eol(), self.y)
+
+            else:
+                # A valid endchoice/endif/endmenu is caught by the 'end_token'
+                # check above
+                self._parse_error(
+                    "no corresponding 'choice'" if t0 is _T_ENDCHOICE else
+                    "no corresponding 'if'"     if t0 is _T_ENDIF else
+                    "no corresponding 'menu'"   if t0 is _T_ENDMENU else
+                    "unrecognized construct")
+
+        # End of file reached. Return the last node.
+
+        if end_token:
+            raise KconfigError(
+                "error: expected '{}' at end of '{}'"
+                .format("endchoice" if end_token is _T_ENDCHOICE else
+                        "endif"     if end_token is _T_ENDIF else
+                        "endmenu",
+                        self.filename))
+
+        return prev
+
+    def _parse_cond(self):
+        # Parses an optional 'if <expr>' construct and returns the parsed
+        # <expr>, or self.y if the next token is not _T_IF
+
+        expr = self._parse_expr(True) if self._check_token(_T_IF) else self.y
+
+        if self._tokens[self._tokens_i] is not None:
+            self._trailing_tokens_error()
+
+        return expr
+
+    def _parse_props(self, node):
+        # Parses and adds properties to the MenuNode 'node' (type, 'prompt',
+        # 'default's, etc.) Properties are later copied up to symbols and
+        # choices in a separate pass after parsing, in e.g.
+        # _add_props_to_sym().
+        #
+        # An older version of this code added properties directly to symbols
+        # and choices instead of to their menu nodes (and handled dependency
+        # propagation simultaneously), but that loses information on where a
+        # property is added when a symbol or choice is defined in multiple
+        # locations. Some Kconfig configuration systems rely heavily on such
+        # symbols, and better docs can be generated by keeping track of where
+        # properties are added.
+        #
+        # node:
+        #   The menu node we're parsing properties on
+
+        # Dependencies from 'depends on'. Will get propagated to the properties
+        # below.
+        node.dep = self.y
+
+        while self._next_line():
+            t0 = self._tokens[0]
+
+            if t0 in _TYPE_TOKENS:
+                # Relies on '_T_BOOL is BOOL', etc., to save a conversion
+                self._set_type(node.item, t0)
+                if self._tokens[1] is not None:
+                    self._parse_prompt(node)
+
+            elif t0 is _T_DEPENDS:
+                if not self._check_token(_T_ON):
+                    self._parse_error("expected 'on' after 'depends'")
+
+                node.dep = self._make_and(node.dep,
+                                          self._expect_expr_and_eol())
+
+            elif t0 is _T_HELP:
+                self._parse_help(node)
+
+            elif t0 is _T_SELECT:
+                if node.item.__class__ is not Symbol:
+                    self._parse_error("only symbols can select")
+
+                node.selects.append((self._expect_nonconst_sym(),
+                                     self._parse_cond()))
+
+            elif t0 is None:
+                # Blank line
+                continue
+
+            elif t0 is _T_DEFAULT:
+                node.defaults.append((self._parse_expr(False),
+                                      self._parse_cond()))
+
+            elif t0 in _DEF_TOKEN_TO_TYPE:
+                self._set_type(node.item, _DEF_TOKEN_TO_TYPE[t0])
+                node.defaults.append((self._parse_expr(False),
+                                      self._parse_cond()))
+
+            elif t0 is _T_PROMPT:
+                self._parse_prompt(node)
+
+            elif t0 is _T_RANGE:
+                node.ranges.append((self._expect_sym(), self._expect_sym(),
+                                    self._parse_cond()))
+
+            elif t0 is _T_IMPLY:
+                if node.item.__class__ is not Symbol:
+                    self._parse_error("only symbols can imply")
+
+                node.implies.append((self._expect_nonconst_sym(),
+                                     self._parse_cond()))
+
+            elif t0 is _T_VISIBLE:
+                if not self._check_token(_T_IF):
+                    self._parse_error("expected 'if' after 'visible'")
+
+                node.visibility = self._make_and(node.visibility,
+                                                 self._expect_expr_and_eol())
+
+            elif t0 is _T_OPTION:
+                if self._check_token(_T_ENV):
+                    if not self._check_token(_T_EQUAL):
+                        self._parse_error("expected '=' after 'env'")
+
+                    env_var = self._expect_str_and_eol()
+                    node.item.env_var = env_var
+
+                    if env_var in os.environ:
+                        node.defaults.append(
+                            (self._lookup_const_sym(os.environ[env_var]),
+                             self.y))
+                    else:
+                        self._warn("{1} has 'option env=\"{0}\"', "
+                                   "but the environment variable {0} is not "
+                                   "set".format(node.item.name, env_var),
+                                   self.filename, self.linenr)
+
+                    if env_var != node.item.name:
+                        self._warn("Kconfiglib expands environment variables "
+                                   "in strings directly, meaning you do not "
+                                   "need 'option env=...' \"bounce\" symbols. "
+                                   "For compatibility with the C tools, "
+                                   "rename {} to {} (so that the symbol name "
+                                   "matches the environment variable name)."
+                                   .format(node.item.name, env_var),
+                                   self.filename, self.linenr)
+
+                elif self._check_token(_T_DEFCONFIG_LIST):
+                    if not self.defconfig_list:
+                        self.defconfig_list = node.item
+                    else:
+                        self._warn("'option defconfig_list' set on multiple "
+                                   "symbols ({0} and {1}). Only {0} will be "
+                                   "used.".format(self.defconfig_list.name,
+                                                  node.item.name),
+                                   self.filename, self.linenr)
+
+                elif self._check_token(_T_MODULES):
+                    # To reduce warning spam, only warn if 'option modules' is
+                    # set on some symbol that isn't MODULES, which should be
+                    # safe. I haven't run into any projects that make use
+                    # modules besides the kernel yet, and there it's likely to
+                    # keep being called "MODULES".
+                    if node.item is not self.modules:
+                        self._warn("the 'modules' option is not supported. "
+                                   "Let me know if this is a problem for you, "
+                                   "as it wouldn't be that hard to implement. "
+                                   "Note that modules are supported -- "
+                                   "Kconfiglib just assumes the symbol name "
+                                   "MODULES, like older versions of the C "
+                                   "implementation did when 'option modules' "
+                                   "wasn't used.",
+                                   self.filename, self.linenr)
+
+                elif self._check_token(_T_ALLNOCONFIG_Y):
+                    if node.item.__class__ is not Symbol:
+                        self._parse_error("the 'allnoconfig_y' option is only "
+                                          "valid for symbols")
+
+                    node.item.is_allnoconfig_y = True
+
+                else:
+                    self._parse_error("unrecognized option")
+
+            elif t0 is _T_OPTIONAL:
+                if node.item.__class__ is not Choice:
+                    self._parse_error('"optional" is only valid for choices')
+
+                node.item.is_optional = True
+
+            else:
+                # Reuse the tokens for the non-property line later
+                self._reuse_tokens = True
+                return
+
+    def _set_type(self, sc, new_type):
+        # Sets the type of 'sc' (symbol or choice) to 'new_type'
+
+        # UNKNOWN is falsy
+        if sc.orig_type and sc.orig_type is not new_type:
+            self._warn("{} defined with multiple types, {} will be used"
+                       .format(sc.name_and_loc, TYPE_TO_STR[new_type]))
+
+        sc.orig_type = new_type
+
+    def _parse_prompt(self, node):
+        # 'prompt' properties override each other within a single definition of
+        # a symbol, but additional prompts can be added by defining the symbol
+        # multiple times
+
+        if node.prompt:
+            self._warn(node.item.name_and_loc +
+                       " defined with multiple prompts in single location")
+
+        prompt = self._tokens[1]
+        self._tokens_i = 2
+
+        if prompt.__class__ is not str:
+            self._parse_error("expected prompt string")
+
+        if prompt != prompt.strip():
+            self._warn(node.item.name_and_loc +
+                       " has leading or trailing whitespace in its prompt")
+
+            # This avoid issues for e.g. reStructuredText documentation, where
+            # '*prompt *' is invalid
+            prompt = prompt.strip()
+
+        node.prompt = (prompt, self._parse_cond())
+
+    def _parse_help(self, node):
+        if node.help is not None:
+            self._warn(node.item.name_and_loc + " defined with more than "
+                       "one help text -- only the last one will be used")
+
+        # Micro-optimization. This code is pretty hot.
+        readline = self._readline
+
+        # Find first non-blank (not all-space) line and get its
+        # indentation
+
+        while 1:
+            line = readline()
+            self.linenr += 1
+            if not line:
+                self._empty_help(node, line)
+                return
+            if not line.isspace():
+                break
+
+        len_ = len  # Micro-optimization
+
+        # Use a separate 'expline' variable here and below to avoid stomping on
+        # any tabs people might've put deliberately into the first line after
+        # the help text
+        expline = line.expandtabs()
+        indent = len_(expline) - len_(expline.lstrip())
+        if not indent:
+            self._empty_help(node, line)
+            return
+
+        # The help text goes on till the first non-blank line with less indent
+        # than the first line
+
+        # Add the first line
+        lines = [expline[indent:]]
+        add_line = lines.append  # Micro-optimization
+
+        while 1:
+            line = readline()
+            if line.isspace():
+                # No need to preserve the exact whitespace in these
+                add_line("\n")
+            elif not line:
+                # End of file
+                break
+            else:
+                expline = line.expandtabs()
+                if len_(expline) - len_(expline.lstrip()) < indent:
+                    break
+                add_line(expline[indent:])
+
+        self.linenr += len_(lines)
+        node.help = "".join(lines).rstrip()
+        if line:
+            self._line_after_help(line)
+
+    def _empty_help(self, node, line):
+        self._warn(node.item.name_and_loc +
+                   " has 'help' but empty help text")
+        node.help = ""
+        if line:
+            self._line_after_help(line)
+
+    def _parse_expr(self, transform_m):
+        # Parses an expression from the tokens in Kconfig._tokens using a
+        # simple top-down approach. See the module docstring for the expression
+        # format.
+        #
+        # transform_m:
+        #   True if m should be rewritten to m && MODULES. See the
+        #   Kconfig.eval_string() documentation.
+
+        # Grammar:
+        #
+        #   expr:     and_expr ['||' expr]
+        #   and_expr: factor ['&&' and_expr]
+        #   factor:   <symbol> ['='/'!='/'<'/... <symbol>]
+        #             '!' factor
+        #             '(' expr ')'
+        #
+        # It helps to think of the 'expr: and_expr' case as a single-operand OR
+        # (no ||), and of the 'and_expr: factor' case as a single-operand AND
+        # (no &&). Parsing code is always a bit tricky.
+
+        # Mind dump: parse_factor() and two nested loops for OR and AND would
+        # work as well. The straightforward implementation there gives a
+        # (op, (op, (op, A, B), C), D) parse for A op B op C op D. Representing
+        # expressions as (op, [list of operands]) instead goes nicely with that
+        # version, but is wasteful for short expressions and complicates
+        # expression evaluation and other code that works on expressions (more
+        # complicated code likely offsets any performance gain from less
+        # recursion too). If we also try to optimize the list representation by
+        # merging lists when possible (e.g. when ANDing two AND expressions),
+        # we end up allocating a ton of lists instead of reusing expressions,
+        # which is bad.
+
+        and_expr = self._parse_and_expr(transform_m)
+
+        # Return 'and_expr' directly if we have a "single-operand" OR.
+        # Otherwise, parse the expression on the right and make an OR node.
+        # This turns A || B || C || D into (OR, A, (OR, B, (OR, C, D))).
+        return and_expr if not self._check_token(_T_OR) else \
+            (OR, and_expr, self._parse_expr(transform_m))
+
+    def _parse_and_expr(self, transform_m):
+        factor = self._parse_factor(transform_m)
+
+        # Return 'factor' directly if we have a "single-operand" AND.
+        # Otherwise, parse the right operand and make an AND node. This turns
+        # A && B && C && D into (AND, A, (AND, B, (AND, C, D))).
+        return factor if not self._check_token(_T_AND) else \
+            (AND, factor, self._parse_and_expr(transform_m))
+
+    def _parse_factor(self, transform_m):
+        token = self._tokens[self._tokens_i]
+        self._tokens_i += 1
+
+        if token.__class__ is Symbol:
+            # Plain symbol or relation
+
+            if self._tokens[self._tokens_i] not in _RELATIONS:
+                # Plain symbol
+
+                # For conditional expressions ('depends on <expr>',
+                # '... if <expr>', etc.), m is rewritten to m && MODULES.
+                if transform_m and token is self.m:
+                    return (AND, self.m, self.modules)
+
+                return token
+
+            # Relation
+            #
+            # _T_EQUAL, _T_UNEQUAL, etc., deliberately have the same values as
+            # EQUAL, UNEQUAL, etc., so we can just use the token directly
+            self._tokens_i += 1
+            return (self._tokens[self._tokens_i - 1], token,
+                    self._expect_sym())
+
+        if token is _T_NOT:
+            # token == _T_NOT == NOT
+            return (token, self._parse_factor(transform_m))
+
+        if token is _T_OPEN_PAREN:
+            expr_parse = self._parse_expr(transform_m)
+            if self._check_token(_T_CLOSE_PAREN):
+                return expr_parse
+
+        self._parse_error("malformed expression")
+
+    #
+    # Caching and invalidation
+    #
+
+    def _build_dep(self):
+        # Populates the Symbol/Choice._dependents sets, which contain all other
+        # items (symbols and choices) that immediately depend on the item in
+        # the sense that changing the value of the item might affect the value
+        # of the dependent items. This is used for caching/invalidation.
+        #
+        # The calculated sets might be larger than necessary as we don't do any
+        # complex analysis of the expressions.
+
+        depend_on = _depend_on  # Micro-optimization
+
+        # Only calculate _dependents for defined symbols. Constant and
+        # undefined symbols could theoretically be selected/implied, but it
+        # wouldn't change their value, so it's not a true dependency.
+        for sym in self.unique_defined_syms:
+            # Symbols depend on the following:
+
+            # The prompt conditions
+            for node in sym.nodes:
+                if node.prompt:
+                    depend_on(sym, node.prompt[1])
+
+            # The default values and their conditions
+            for value, cond in sym.defaults:
+                depend_on(sym, value)
+                depend_on(sym, cond)
+
+            # The reverse and weak reverse dependencies
+            depend_on(sym, sym.rev_dep)
+            depend_on(sym, sym.weak_rev_dep)
+
+            # The ranges along with their conditions
+            for low, high, cond in sym.ranges:
+                depend_on(sym, low)
+                depend_on(sym, high)
+                depend_on(sym, cond)
+
+            # The direct dependencies. This is usually redundant, as the direct
+            # dependencies get propagated to properties, but it's needed to get
+            # invalidation solid for 'imply', which only checks the direct
+            # dependencies (even if there are no properties to propagate it
+            # to).
+            depend_on(sym, sym.direct_dep)
+
+            # In addition to the above, choice symbols depend on the choice
+            # they're in, but that's handled automatically since the Choice is
+            # propagated to the conditions of the properties before
+            # _build_dep() runs.
+
+        for choice in self.unique_choices:
+            # Choices depend on the following:
+
+            # The prompt conditions
+            for node in choice.nodes:
+                if node.prompt:
+                    depend_on(choice, node.prompt[1])
+
+            # The default symbol conditions
+            for _, cond in choice.defaults:
+                depend_on(choice, cond)
+
+    def _add_choice_deps(self):
+        # Choices also depend on the choice symbols themselves, because the
+        # y-mode selection of the choice might change if a choice symbol's
+        # visibility changes.
+        #
+        # We add these dependencies separately after dependency loop detection.
+        # The invalidation algorithm can handle the resulting
+        # <choice symbol> <-> <choice> dependency loops, but they make loop
+        # detection awkward.
+
+        for choice in self.unique_choices:
+            for sym in choice.syms:
+                sym._dependents.add(choice)
+
+    def _invalidate_all(self):
+        # Undefined symbols never change value and don't need to be
+        # invalidated, so we can just iterate over defined symbols.
+        # Invalidating constant symbols would break things horribly.
+        for sym in self.unique_defined_syms:
+            sym._invalidate()
+
+        for choice in self.unique_choices:
+            choice._invalidate()
+
+    #
+    # Post-parsing menu tree processing, including dependency propagation and
+    # implicit submenu creation
+    #
+
+    def _finalize_node(self, node, visible_if):
+        # Finalizes a menu node and its children:
+        #
+        #  - Copies properties from menu nodes up to their contained
+        #    symbols/choices
+        #
+        #  - Propagates dependencies from parent to child nodes
+        #
+        #  - Creates implicit menus (see kconfig-language.txt)
+        #
+        #  - Removes 'if' nodes
+        #
+        #  - Sets 'choice' types and registers choice symbols
+        #
+        # menu_finalize() in the C implementation is similar.
+        #
+        # node:
+        #   The menu node to finalize. This node and its children will have
+        #   been finalized when the function returns, and any implicit menus
+        #   will have been created.
+        #
+        # visible_if:
+        #   Dependencies from 'visible if' on parent menus. These are added to
+        #   the prompts of symbols and choices.
+
+        if node.item.__class__ is Symbol:
+            # Copy defaults, ranges, selects, and implies to the Symbol
+            self._add_props_to_sym(node)
+
+            # Find any items that should go in an implicit menu rooted at the
+            # symbol
+            cur = node
+            while cur.next and _auto_menu_dep(node, cur.next):
+                # This makes implicit submenu creation work recursively, with
+                # implicit menus inside implicit menus
+                self._finalize_node(cur.next, visible_if)
+                cur = cur.next
+                cur.parent = node
+
+            if cur is not node:
+                # Found symbols that should go in an implicit submenu. Tilt
+                # them up above us.
+                node.list = node.next
+                node.next = cur.next
+                cur.next = None
+
+        elif node.list:
+            # The menu node is a choice, menu, or if. Finalize each child node.
+
+            if node.item is MENU:
+                visible_if = self._make_and(visible_if, node.visibility)
+
+            # Propagate the menu node's dependencies to each child menu node.
+            #
+            # This needs to go before the recursive _finalize_node() call so
+            # that implicit submenu creation can look ahead at dependencies.
+            self._propagate_deps(node, visible_if)
+
+            # Finalize the children
+            cur = node.list
+            while cur:
+                self._finalize_node(cur, visible_if)
+                cur = cur.next
+
+        if node.list:
+            # node's children have been individually finalized. Do final steps
+            # to finalize this "level" in the menu tree.
+            _flatten(node.list)
+            _remove_ifs(node)
+
+        # Empty choices (node.list None) are possible, so this needs to go
+        # outside
+        if node.item.__class__ is Choice:
+            # Add the node's non-node-specific properties to the choice, like
+            # _add_props_to_sym() does
+            choice = node.item
+            choice.direct_dep = self._make_or(choice.direct_dep, node.dep)
+            choice.defaults += node.defaults
+
+            _finalize_choice(node)
+
+    def _propagate_deps(self, node, visible_if):
+        # Propagates 'node's dependencies to its child menu nodes
+
+        # If the parent node holds a Choice, we use the Choice itself as the
+        # parent dependency. This makes sense as the value (mode) of the choice
+        # limits the visibility of the contained choice symbols. The C
+        # implementation works the same way.
+        #
+        # Due to the similar interface, Choice works as a drop-in replacement
+        # for Symbol here.
+        basedep = node.item if node.item.__class__ is Choice else node.dep
+
+        cur = node.list
+        while cur:
+            dep = cur.dep = self._make_and(cur.dep, basedep)
+
+            if cur.item.__class__ in _SYMBOL_CHOICE:
+                # Propagate 'visible if' and dependencies to the prompt
+                if cur.prompt:
+                    cur.prompt = (cur.prompt[0],
+                                  self._make_and(
+                                      cur.prompt[1],
+                                      self._make_and(visible_if, dep)))
+
+                # Propagate dependencies to defaults
+                if cur.defaults:
+                    cur.defaults = [(default, self._make_and(cond, dep))
+                                    for default, cond in cur.defaults]
+
+                # Propagate dependencies to ranges
+                if cur.ranges:
+                    cur.ranges = [(low, high, self._make_and(cond, dep))
+                                  for low, high, cond in cur.ranges]
+
+                # Propagate dependencies to selects
+                if cur.selects:
+                    cur.selects = [(target, self._make_and(cond, dep))
+                                   for target, cond in cur.selects]
+
+                # Propagate dependencies to implies
+                if cur.implies:
+                    cur.implies = [(target, self._make_and(cond, dep))
+                                   for target, cond in cur.implies]
+
+            elif cur.prompt:  # Not a symbol/choice
+                # Propagate dependencies to the prompt. 'visible if' is only
+                # propagated to symbols/choices.
+                cur.prompt = (cur.prompt[0],
+                              self._make_and(cur.prompt[1], dep))
+
+            cur = cur.next
+
+    def _add_props_to_sym(self, node):
+        # Copies properties from the menu node 'node' up to its contained
+        # symbol, and adds (weak) reverse dependencies to selected/implied
+        # symbols.
+        #
+        # This can't be rolled into _propagate_deps(), because that function
+        # traverses the menu tree roughly breadth-first, meaning properties on
+        # symbols defined in multiple locations could end up in the wrong
+        # order.
+
+        sym = node.item
+
+        # See the Symbol class docstring
+        sym.direct_dep = self._make_or(sym.direct_dep, node.dep)
+
+        sym.defaults += node.defaults
+        sym.ranges += node.ranges
+        sym.selects += node.selects
+        sym.implies += node.implies
+
+        # Modify the reverse dependencies of the selected symbol
+        for target, cond in node.selects:
+            target.rev_dep = self._make_or(
+                target.rev_dep,
+                self._make_and(sym, cond))
+
+        # Modify the weak reverse dependencies of the implied
+        # symbol
+        for target, cond in node.implies:
+            target.weak_rev_dep = self._make_or(
+                target.weak_rev_dep,
+                self._make_and(sym, cond))
+
+    #
+    # Misc.
+    #
+
+    def _check_sym_sanity(self):
+        # Checks various symbol properties that are handiest to check after
+        # parsing. Only generates errors and warnings.
+
+        def num_ok(sym, type_):
+            # Returns True if the (possibly constant) symbol 'sym' is valid as a value
+            # for a symbol of type type_ (INT or HEX)
+
+            # 'not sym.nodes' implies a constant or undefined symbol, e.g. a plain
+            # "123"
+            if not sym.nodes:
+                return _is_base_n(sym.name, _TYPE_TO_BASE[type_])
+
+            return sym.orig_type is type_
+
+        for sym in self.unique_defined_syms:
+            if sym.orig_type in _BOOL_TRISTATE:
+                # A helper function could be factored out here, but keep it
+                # speedy/straightforward
+
+                for target_sym, _ in sym.selects:
+                    if target_sym.orig_type not in _BOOL_TRISTATE_UNKNOWN:
+                        self._warn("{} selects the {} symbol {}, which is not "
+                                   "bool or tristate"
+                                   .format(sym.name_and_loc,
+                                           TYPE_TO_STR[target_sym.orig_type],
+                                           target_sym.name_and_loc))
+
+                for target_sym, _ in sym.implies:
+                    if target_sym.orig_type not in _BOOL_TRISTATE_UNKNOWN:
+                        self._warn("{} implies the {} symbol {}, which is not "
+                                   "bool or tristate"
+                                   .format(sym.name_and_loc,
+                                           TYPE_TO_STR[target_sym.orig_type],
+                                           target_sym.name_and_loc))
+
+            elif sym.orig_type:  # STRING/INT/HEX
+                for default, _ in sym.defaults:
+                    if default.__class__ is not Symbol:
+                        raise KconfigError(
+                            "the {} symbol {} has a malformed default {} -- "
+                            "expected a single symbol"
+                            .format(TYPE_TO_STR[sym.orig_type],
+                                    sym.name_and_loc, expr_str(default)))
+
+                    if sym.orig_type is STRING:
+                        if not default.is_constant and not default.nodes and \
+                           not default.name.isupper():
+                            # 'default foo' on a string symbol could be either a symbol
+                            # reference or someone leaving out the quotes. Guess that
+                            # the quotes were left out if 'foo' isn't all-uppercase
+                            # (and no symbol named 'foo' exists).
+                            self._warn("style: quotes recommended around "
+                                       "default value for string symbol "
+                                       + sym.name_and_loc)
+
+                    elif not num_ok(default, sym.orig_type):  # INT/HEX
+                        self._warn("the {0} symbol {1} has a non-{0} default {2}"
+                                   .format(TYPE_TO_STR[sym.orig_type],
+                                           sym.name_and_loc,
+                                           default.name_and_loc))
+
+                if sym.selects or sym.implies:
+                    self._warn("the {} symbol {} has selects or implies"
+                               .format(TYPE_TO_STR[sym.orig_type],
+                                       sym.name_and_loc))
+
+            else:  # UNKNOWN
+                self._warn("{} defined without a type"
+                           .format(sym.name_and_loc))
+
+
+            if sym.ranges:
+                if sym.orig_type not in _INT_HEX:
+                    self._warn(
+                        "the {} symbol {} has ranges, but is not int or hex"
+                        .format(TYPE_TO_STR[sym.orig_type],
+                                sym.name_and_loc))
+                else:
+                    for low, high, _ in sym.ranges:
+                        if not num_ok(low, sym.orig_type) or \
+                           not num_ok(high, sym.orig_type):
+
+                            self._warn("the {0} symbol {1} has a non-{0} "
+                                       "range [{2}, {3}]"
+                                       .format(TYPE_TO_STR[sym.orig_type],
+                                               sym.name_and_loc,
+                                               low.name_and_loc,
+                                               high.name_and_loc))
+
+    def _check_choice_sanity(self):
+        # Checks various choice properties that are handiest to check after
+        # parsing. Only generates errors and warnings.
+
+        def warn_select_imply(sym, expr, expr_type):
+            msg = "the choice symbol {} is {} by the following symbols, but " \
+                  "select/imply has no effect on choice symbols" \
+                  .format(sym.name_and_loc, expr_type)
+
+            # si = select/imply
+            for si in split_expr(expr, OR):
+                msg += "\n - " + split_expr(si, AND)[0].name_and_loc
+
+            self._warn(msg)
+
+        for choice in self.unique_choices:
+            if choice.orig_type not in _BOOL_TRISTATE:
+                self._warn("{} defined with type {}"
+                           .format(choice.name_and_loc,
+                                   TYPE_TO_STR[choice.orig_type]))
+
+            for node in choice.nodes:
+                if node.prompt:
+                    break
+            else:
+                self._warn(choice.name_and_loc + " defined without a prompt")
+
+            for default, _ in choice.defaults:
+                if default.__class__ is not Symbol:
+                    raise KconfigError(
+                        "{} has a malformed default {}"
+                        .format(choice.name_and_loc, expr_str(default)))
+
+                if default.choice is not choice:
+                    self._warn("the default selection {} of {} is not "
+                               "contained in the choice"
+                               .format(default.name_and_loc,
+                                       choice.name_and_loc))
+
+            for sym in choice.syms:
+                if sym.defaults:
+                    self._warn("default on the choice symbol {} will have "
+                               "no effect, as defaults do not affect choice "
+                               "symbols".format(sym.name_and_loc))
+
+                if sym.rev_dep is not sym.kconfig.n:
+                    warn_select_imply(sym, sym.rev_dep, "selected")
+
+                if sym.weak_rev_dep is not sym.kconfig.n:
+                    warn_select_imply(sym, sym.weak_rev_dep, "implied")
+
+                for node in sym.nodes:
+                    if node.parent.item is choice:
+                        if not node.prompt:
+                            self._warn("the choice symbol {} has no prompt"
+                                       .format(sym.name_and_loc))
+
+                    elif node.prompt:
+                        self._warn("the choice symbol {} is defined with a "
+                                   "prompt outside the choice"
+                                   .format(sym.name_and_loc))
+
+    def _parse_error(self, msg):
+        raise KconfigError("{}error: couldn't parse '{}': {}".format(
+            "" if self.filename is None else
+                "{}:{}: ".format(self.filename, self.linenr),
+            self._line.strip(), msg))
+
+    def _trailing_tokens_error(self):
+        self._parse_error("extra tokens at end of line")
+
+    def _open(self, filename, mode):
+        # open() wrapper:
+        #
+        # - Enable universal newlines mode on Python 2 to ease
+        #   interoperability between Linux and Windows. It's already the
+        #   default on Python 3.
+        #
+        #   The "U" flag would currently work for both Python 2 and 3, but it's
+        #   deprecated on Python 3, so play it future-safe.
+        #
+        #   io.open() defaults to universal newlines on Python 2 (and is an
+        #   alias for open() on Python 3), but it returns 'unicode' strings and
+        #   slows things down:
+        #
+        #     Parsing x86 Kconfigs on Python 2
+        #
+        #     with open(..., "rU"):
+        #
+        #       real  0m0.930s
+        #       user  0m0.905s
+        #       sys   0m0.025s
+        #
+        #     with io.open():
+        #
+        #       real  0m1.069s
+        #       user  0m1.040s
+        #       sys   0m0.029s
+        #
+        #   There's no appreciable performance difference between "r" and
+        #   "rU" for parsing performance on Python 2.
+        #
+        # - For Python 3, force the encoding. Forcing the encoding on Python 2
+        #   turns strings into Unicode strings, which gets messy. Python 2
+        #   doesn't decode regular strings anyway.
+        return open(filename, "rU" if mode == "r" else mode) if _IS_PY2 else \
+               open(filename, mode, encoding=self._encoding)
+
+    def _check_undef_syms(self):
+        # Prints warnings for all references to undefined symbols within the
+        # Kconfig files
+
+        def is_num(s):
+            # Returns True if the string 's' looks like a number.
+            #
+            # Internally, all operands in Kconfig are symbols, only undefined symbols
+            # (which numbers usually are) get their name as their value.
+            #
+            # Only hex numbers that start with 0x/0X are classified as numbers.
+            # Otherwise, symbols whose names happen to contain only the letters A-F
+            # would trigger false positives.
+
+            try:
+                int(s)
+            except ValueError:
+                if not s.startswith(("0x", "0X")):
+                    return False
+
+                try:
+                    int(s, 16)
+                except ValueError:
+                    return False
+
+            return True
+
+        for sym in (self.syms.viewvalues if _IS_PY2 else self.syms.values)():
+            # - sym.nodes empty means the symbol is undefined (has no
+            #   definition locations)
+            #
+            # - Due to Kconfig internals, numbers show up as undefined Kconfig
+            #   symbols, but shouldn't be flagged
+            #
+            # - The MODULES symbol always exists
+            if not sym.nodes and not is_num(sym.name) and \
+               sym.name != "MODULES":
+
+                msg = "undefined symbol {}:".format(sym.name)
+                for node in self.node_iter():
+                    if sym in node.referenced:
+                        msg += "\n\n- Referenced at {}:{}:\n\n{}" \
+                               .format(node.filename, node.linenr, node)
+                self._warn(msg)
+
+    def _warn(self, msg, filename=None, linenr=None):
+        # For printing general warnings
+
+        if not self.warn:
+            return
+
+        msg = "warning: " + msg
+        if filename is not None:
+            msg = "{}:{}: {}".format(filename, linenr, msg)
+
+        self.warnings.append(msg)
+        if self.warn_to_stderr:
+            sys.stderr.write(msg + "\n")
+
+
+class Symbol(object):
+    """
+    Represents a configuration symbol:
+
+      (menu)config FOO
+          ...
+
+    The following attributes are available. They should be viewed as read-only,
+    and some are implemented through @property magic (but are still efficient
+    to access due to internal caching).
+
+    Note: Prompts, help texts, and locations are stored in the Symbol's
+    MenuNode(s) rather than in the Symbol itself. Check the MenuNode class and
+    the Symbol.nodes attribute. This organization matches the C tools.
+
+    name:
+      The name of the symbol, e.g. "FOO" for 'config FOO'.
+
+    type:
+      The type of the symbol. One of BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN.
+      UNKNOWN is for undefined symbols, (non-special) constant symbols, and
+      symbols defined without a type.
+
+      When running without modules (MODULES having the value n), TRISTATE
+      symbols magically change type to BOOL. This also happens for symbols
+      within choices in "y" mode. This matches the C tools, and makes sense for
+      menuconfig-like functionality.
+
+    orig_type:
+      The type as given in the Kconfig file, without any magic applied. Used
+      when printing the symbol.
+
+    tri_value:
+      The tristate value of the symbol as an integer. One of 0, 1, 2,
+      representing n, m, y. Always 0 (n) for non-bool/tristate symbols.
+
+      This is the symbol value that's used outside of relation expressions
+      (A, !A, A && B, A || B).
+
+    str_value:
+      The value of the symbol as a string. Gives the value for string/int/hex
+      symbols. For bool/tristate symbols, gives "n", "m", or "y".
+
+      This is the symbol value that's used in relational expressions
+      (A = B, A != B, etc.)
+
+      Gotcha: For int/hex symbols, the exact format of the value is often
+      preserved (e.g. when writing a .config file), hence why you can't get it
+      directly as an int. Do int(int_sym.str_value) or
+      int(hex_sym.str_value, 16) to get the integer value.
+
+    user_value:
+      The user value of the symbol. None if no user value has been assigned
+      (via Kconfig.load_config() or Symbol.set_value()).
+
+      Holds 0, 1, or 2 for bool/tristate symbols, and a string for the other
+      symbol types.
+
+      WARNING: Do not assign directly to this. It will break things. Use
+      Symbol.set_value().
+
+    assignable:
+      A tuple containing the tristate user values that can currently be
+      assigned to the symbol (that would be respected), ordered from lowest (0,
+      representing n) to highest (2, representing y). This corresponds to the
+      selections available in the menuconfig interface. The set of assignable
+      values is calculated from the symbol's visibility and selects/implies.
+
+      Returns the empty set for non-bool/tristate symbols and for symbols with
+      visibility n. The other possible values are (0, 2), (0, 1, 2), (1, 2),
+      (1,), and (2,). A (1,) or (2,) result means the symbol is visible but
+      "locked" to m or y through a select, perhaps in combination with the
+      visibility. menuconfig represents this as -M- and -*-, respectively.
+
+      For string/hex/int symbols, check if Symbol.visibility is non-0 (non-n)
+      instead to determine if the value can be changed.
+
+      Some handy 'assignable' idioms:
+
+        # Is 'sym' an assignable (visible) bool/tristate symbol?
+        if sym.assignable:
+            # What's the highest value it can be assigned? [-1] in Python
+            # gives the last element.
+            sym_high = sym.assignable[-1]
+
+            # The lowest?
+            sym_low = sym.assignable[0]
+
+            # Can the symbol be set to at least m?
+            if sym.assignable[-1] >= 1:
+                ...
+
+        # Can the symbol be set to m?
+        if 1 in sym.assignable:
+            ...
+
+    visibility:
+      The visibility of the symbol. One of 0, 1, 2, representing n, m, y. See
+      the module documentation for an overview of symbol values and visibility.
+
+    config_string:
+      The .config assignment string that would get written out for the symbol
+      by Kconfig.write_config(). Returns the empty string if no .config
+      assignment would get written out.
+
+      In general, visible symbols, symbols with (active) defaults, and selected
+      symbols get written out. This includes all non-n-valued bool/tristate
+      symbols, and all visible string/int/hex symbols.
+
+      Symbols with the (no longer needed) 'option env=...' option generate no
+      configuration output, and neither does the special
+      'option defconfig_list' symbol.
+
+      Tip: This field is useful when generating custom configuration output,
+      even for non-.config-like formats. To write just the symbols that would
+      get written out to .config files, do this:
+
+        if sym.config_string:
+            *Write symbol, e.g. by looking sym.str_value*
+
+      This is a superset of the symbols written out by write_autoconf().
+      That function skips all n-valued symbols.
+
+      There usually won't be any great harm in just writing all symbols either,
+      though you might get some special symbols and possibly some "redundant"
+      n-valued symbol entries in there.
+
+    name_and_loc:
+      Holds a string like
+
+        "MY_SYMBOL (defined at foo/Kconfig:12, bar/Kconfig:14)"
+
+      , giving the name of the symbol and its definition location(s).
+
+      If the symbol is undefined, the location is given as "(undefined)".
+
+    nodes:
+      A list of MenuNodes for this symbol. Will contain a single MenuNode for
+      most symbols. Undefined and constant symbols have an empty nodes list.
+      Symbols defined in multiple locations get one node for each location.
+
+    choice:
+      Holds the parent Choice for choice symbols, and None for non-choice
+      symbols. Doubles as a flag for whether a symbol is a choice symbol.
+
+    defaults:
+      List of (default, cond) tuples for the symbol's 'default' properties. For
+      example, 'default A && B if C || D' is represented as
+      ((AND, A, B), (OR, C, D)). If no condition was given, 'cond' is
+      self.kconfig.y.
+
+      Note that 'depends on' and parent dependencies are propagated to
+      'default' conditions.
+
+    selects:
+      List of (symbol, cond) tuples for the symbol's 'select' properties. For
+      example, 'select A if B && C' is represented as (A, (AND, B, C)). If no
+      condition was given, 'cond' is self.kconfig.y.
+
+      Note that 'depends on' and parent dependencies are propagated to 'select'
+      conditions.
+
+    implies:
+      Like 'selects', for imply.
+
+    ranges:
+      List of (low, high, cond) tuples for the symbol's 'range' properties. For
+      example, 'range 1 2 if A' is represented as (1, 2, A). If there is no
+      condition, 'cond' is self.kconfig.y.
+
+      Note that 'depends on' and parent dependencies are propagated to 'range'
+      conditions.
+
+      Gotcha: 1 and 2 above will be represented as (undefined) Symbols rather
+      than plain integers. Undefined symbols get their name as their string
+      value, so this works out. The C tools work the same way.
+
+    orig_defaults:
+    orig_selects:
+    orig_implies:
+    orig_ranges:
+      See the corresponding attributes on the MenuNode class.
+
+    rev_dep:
+      Reverse dependency expression from other symbols selecting this symbol.
+      Multiple selections get ORed together. A condition on a select is ANDed
+      with the selecting symbol.
+
+      For example, if A has 'select FOO' and B has 'select FOO if C', then
+      FOO's rev_dep will be (OR, A, (AND, B, C)).
+
+    weak_rev_dep:
+      Like rev_dep, for imply.
+
+    direct_dep:
+      The direct ('depends on') dependencies for the symbol, or self.kconfig.y
+      if there are no direct dependencies.
+
+      This attribute includes any dependencies from surrounding menus and ifs.
+      Those get propagated to the direct dependencies, and the resulting direct
+      dependencies in turn get propagated to the conditions of all properties.
+
+      If the symbol is defined in multiple locations, the dependencies from the
+      different locations get ORed together.
+
+    referenced:
+      A set() with all symbols and choices referenced in the properties and
+      property conditions of the symbol.
+
+      Also includes dependencies from surrounding menus and ifs, because those
+      get propagated to the symbol (see the 'Intro to symbol values' section in
+      the module docstring).
+
+      Choices appear in the dependencies of choice symbols.
+
+      For the following definitions, only B and not C appears in A's
+      'referenced'. To get transitive references, you'll have to recursively
+      expand 'references' until no new items appear.
+
+        config A
+                bool
+                depends on B
+
+        config B
+                bool
+                depends on C
+
+        config C
+                bool
+
+      See the Symbol.direct_dep attribute if you're only interested in the
+      direct dependencies of the symbol (its 'depends on'). You can extract the
+      symbols in it with the global expr_items() function.
+
+    env_var:
+      If the Symbol has an 'option env="FOO"' option, this contains the name
+      ("FOO") of the environment variable. None for symbols without no
+      'option env'.
+
+      'option env="FOO"' acts like a 'default' property whose value is the
+      value of $FOO.
+
+      Symbols with 'option env' are never written out to .config files, even if
+      they are visible. env_var corresponds to a flag called SYMBOL_AUTO in the
+      C implementation.
+
+    is_allnoconfig_y:
+      True if the symbol has 'option allnoconfig_y' set on it. This has no
+      effect internally (except when printing symbols), but can be checked by
+      scripts.
+
+    is_constant:
+      True if the symbol is a constant (quoted) symbol.
+
+    kconfig:
+      The Kconfig instance this symbol is from.
+    """
+    __slots__ = (
+        "_cached_assignable",
+        "_cached_str_val",
+        "_cached_tri_val",
+        "_cached_vis",
+        "_dependents",
+        "_old_val",
+        "_visited",
+        "_was_set",
+        "_write_to_conf",
+        "choice",
+        "defaults",
+        "direct_dep",
+        "env_var",
+        "implies",
+        "is_allnoconfig_y",
+        "is_constant",
+        "kconfig",
+        "name",
+        "nodes",
+        "orig_type",
+        "ranges",
+        "rev_dep",
+        "selects",
+        "user_value",
+        "weak_rev_dep",
+    )
+
+    #
+    # Public interface
+    #
+
+    @property
+    def type(self):
+        """
+        See the class documentation.
+        """
+        if self.orig_type is TRISTATE and \
+           (self.choice and self.choice.tri_value == 2 or
+            not self.kconfig.modules.tri_value):
+
+            return BOOL
+
+        return self.orig_type
+
+    @property
+    def str_value(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_str_val is not None:
+            return self._cached_str_val
+
+        if self.orig_type in _BOOL_TRISTATE:
+            # Also calculates the visibility, so invalidation safe
+            self._cached_str_val = TRI_TO_STR[self.tri_value]
+            return self._cached_str_val
+
+        # As a quirk of Kconfig, undefined symbols get their name as their
+        # string value. This is why things like "FOO = bar" work for seeing if
+        # FOO has the value "bar".
+        if not self.orig_type:  # UNKNOWN
+            self._cached_str_val = self.name
+            return self.name
+
+        val = ""
+        # Warning: See Symbol._rec_invalidate(), and note that this is a hidden
+        # function call (property magic)
+        vis = self.visibility
+
+        self._write_to_conf = (vis != 0)
+
+        if self.orig_type in _INT_HEX:
+            # The C implementation checks the user value against the range in a
+            # separate code path (post-processing after loading a .config).
+            # Checking all values here instead makes more sense for us. It
+            # requires that we check for a range first.
+
+            base = _TYPE_TO_BASE[self.orig_type]
+
+            # Check if a range is in effect
+            for low_expr, high_expr, cond in self.ranges:
+                if expr_value(cond):
+                    has_active_range = True
+
+                    # The zeros are from the C implementation running strtoll()
+                    # on empty strings
+                    low = int(low_expr.str_value, base) if \
+                      _is_base_n(low_expr.str_value, base) else 0
+                    high = int(high_expr.str_value, base) if \
+                      _is_base_n(high_expr.str_value, base) else 0
+
+                    break
+            else:
+                has_active_range = False
+
+            # Defaults are used if the symbol is invisible, lacks a user value,
+            # or has an out-of-range user value
+            use_defaults = True
+
+            if vis and self.user_value:
+                user_val = int(self.user_value, base)
+                if has_active_range and not low <= user_val <= high:
+                    num2str = str if base == 10 else hex
+                    self.kconfig._warn(
+                        "user value {} on the {} symbol {} ignored due to "
+                        "being outside the active range ([{}, {}]) -- falling "
+                        "back on defaults"
+                        .format(num2str(user_val), TYPE_TO_STR[self.orig_type],
+                                self.name_and_loc,
+                                num2str(low), num2str(high)))
+                else:
+                    # If the user value is well-formed and satisfies range
+                    # contraints, it is stored in exactly the same form as
+                    # specified in the assignment (with or without "0x", etc.)
+                    val = self.user_value
+                    use_defaults = False
+
+            if use_defaults:
+                # No user value or invalid user value. Look at defaults.
+
+                # Used to implement the warning below
+                has_default = False
+
+                for sym, cond in self.defaults:
+                    if expr_value(cond):
+                        has_default = self._write_to_conf = True
+
+                        val = sym.str_value
+
+                        if _is_base_n(val, base):
+                            val_num = int(val, base)
+                        else:
+                            val_num = 0  # strtoll() on empty string
+
+                        break
+                else:
+                    val_num = 0  # strtoll() on empty string
+
+                # This clamping procedure runs even if there's no default
+                if has_active_range:
+                    clamp = None
+                    if val_num < low:
+                        clamp = low
+                    elif val_num > high:
+                        clamp = high
+
+                    if clamp is not None:
+                        # The value is rewritten to a standard form if it is
+                        # clamped
+                        val = str(clamp) \
+                              if self.orig_type is INT else \
+                              hex(clamp)
+
+                        if has_default:
+                            num2str = str if base == 10 else hex
+                            self.kconfig._warn(
+                                "default value {} on {} clamped to {} due to "
+                                "being outside the active range ([{}, {}])"
+                                .format(val_num, self.name_and_loc,
+                                        num2str(clamp), num2str(low),
+                                        num2str(high)))
+
+        elif self.orig_type is STRING:
+            if vis and self.user_value is not None:
+                # If the symbol is visible and has a user value, use that
+                val = self.user_value
+            else:
+                # Otherwise, look at defaults
+                for sym, cond in self.defaults:
+                    if expr_value(cond):
+                        val = sym.str_value
+                        self._write_to_conf = True
+                        break
+
+        # env_var corresponds to SYMBOL_AUTO in the C implementation, and is
+        # also set on the defconfig_list symbol there. Test for the
+        # defconfig_list symbol explicitly instead here, to avoid a nonsensical
+        # env_var setting and the defconfig_list symbol being printed
+        # incorrectly. This code is pretty cold anyway.
+        if self.env_var is not None or self is self.kconfig.defconfig_list:
+            self._write_to_conf = False
+
+        self._cached_str_val = val
+        return val
+
+    @property
+    def tri_value(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_tri_val is not None:
+            return self._cached_tri_val
+
+        if self.orig_type not in _BOOL_TRISTATE:
+            if self.orig_type:  # != UNKNOWN
+                # Would take some work to give the location here
+                self.kconfig._warn(
+                    "The {} symbol {} is being evaluated in a logical context "
+                    "somewhere. It will always evaluate to n."
+                    .format(TYPE_TO_STR[self.orig_type], self.name_and_loc))
+
+            self._cached_tri_val = 0
+            return 0
+
+        # Warning: See Symbol._rec_invalidate(), and note that this is a hidden
+        # function call (property magic)
+        vis = self.visibility
+        self._write_to_conf = (vis != 0)
+
+        val = 0
+
+        if not self.choice:
+            # Non-choice symbol
+
+            if vis and self.user_value is not None:
+                # If the symbol is visible and has a user value, use that
+                val = min(self.user_value, vis)
+
+            else:
+                # Otherwise, look at defaults and weak reverse dependencies
+                # (implies)
+
+                for default, cond in self.defaults:
+                    dep_val = expr_value(cond)
+                    if dep_val:
+                        val = min(expr_value(default), dep_val)
+                        if val:
+                            self._write_to_conf = True
+                        break
+
+                # Weak reverse dependencies are only considered if our
+                # direct dependencies are met
+                dep_val = expr_value(self.weak_rev_dep)
+                if dep_val and expr_value(self.direct_dep):
+                    val = max(dep_val, val)
+                    self._write_to_conf = True
+
+            # Reverse (select-related) dependencies take precedence
+            dep_val = expr_value(self.rev_dep)
+            if dep_val:
+                if expr_value(self.direct_dep) < dep_val:
+                    self._warn_select_unsatisfied_deps()
+
+                val = max(dep_val, val)
+                self._write_to_conf = True
+
+            # m is promoted to y for (1) bool symbols and (2) symbols with a
+            # weak_rev_dep (from imply) of y
+            if val == 1 and \
+               (self.type is BOOL or expr_value(self.weak_rev_dep) == 2):
+                val = 2
+
+        elif vis == 2:
+            # Visible choice symbol in y-mode choice. The choice mode limits
+            # the visibility of choice symbols, so it's sufficient to just
+            # check the visibility of the choice symbols themselves.
+            val = 2 if self.choice.selection is self else 0
+
+        elif vis and self.user_value:
+            # Visible choice symbol in m-mode choice, with set non-0 user value
+            val = 1
+
+        self._cached_tri_val = val
+        return val
+
+    @property
+    def assignable(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_assignable is None:
+            self._cached_assignable = self._assignable()
+        return self._cached_assignable
+
+    @property
+    def visibility(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_vis is None:
+            self._cached_vis = _visibility(self)
+        return self._cached_vis
+
+    @property
+    def config_string(self):
+        """
+        See the class documentation.
+        """
+        # _write_to_conf is determined when the value is calculated. This is a
+        # hidden function call due to property magic.
+        val = self.str_value
+        if not self._write_to_conf:
+            return ""
+
+        if self.orig_type in _BOOL_TRISTATE:
+            return "{}{}={}\n" \
+                   .format(self.kconfig.config_prefix, self.name, val) \
+                   if val != "n" else \
+                   "# {}{} is not set\n" \
+                   .format(self.kconfig.config_prefix, self.name)
+
+        if self.orig_type in _INT_HEX:
+            return "{}{}={}\n" \
+                   .format(self.kconfig.config_prefix, self.name, val)
+
+        # sym.orig_type is STRING
+        return '{}{}="{}"\n' \
+               .format(self.kconfig.config_prefix, self.name, escape(val))
+
+    @property
+    def name_and_loc(self):
+        """
+        See the class documentation.
+        """
+        return self.name + " " + _locs(self)
+
+    def set_value(self, value):
+        """
+        Sets the user value of the symbol.
+
+        Equal in effect to assigning the value to the symbol within a .config
+        file. For bool and tristate symbols, use the 'assignable' attribute to
+        check which values can currently be assigned. Setting values outside
+        'assignable' will cause Symbol.user_value to differ from
+        Symbol.str/tri_value (be truncated down or up).
+
+        Setting a choice symbol to 2 (y) sets Choice.user_selection to the
+        choice symbol in addition to setting Symbol.user_value.
+        Choice.user_selection is considered when the choice is in y mode (the
+        "normal" mode).
+
+        Other symbols that depend (possibly indirectly) on this symbol are
+        automatically recalculated to reflect the assigned value.
+
+        value:
+          The user value to give to the symbol. For bool and tristate symbols,
+          n/m/y can be specified either as 0/1/2 (the usual format for tristate
+          values in Kconfiglib) or as one of the strings "n", "m", or "y". For
+          other symbol types, pass a string.
+
+          Note that the value for an int/hex symbol is passed as a string, e.g.
+          "123" or "0x0123". The format of this string is preserved in the
+          output.
+
+          Values that are invalid for the type (such as "foo" or 1 (m) for a
+          BOOL or "0x123" for an INT) are ignored and won't be stored in
+          Symbol.user_value. Kconfiglib will print a warning by default for
+          invalid assignments, and set_value() will return False.
+
+        Returns True if the value is valid for the type of the symbol, and
+        False otherwise. This only looks at the form of the value. For BOOL and
+        TRISTATE symbols, check the Symbol.assignable attribute to see what
+        values are currently in range and would actually be reflected in the
+        value of the symbol. For other symbol types, check whether the
+        visibility is non-n.
+        """
+        if self.orig_type in _BOOL_TRISTATE and value in STR_TO_TRI:
+            value = STR_TO_TRI[value]
+
+        # If the new user value matches the old, nothing changes, and we can
+        # avoid invalidating cached values.
+        #
+        # This optimization is skipped for choice symbols: Setting a choice
+        # symbol's user value to y might change the state of the choice, so it
+        # wouldn't be safe (symbol user values always match the values set in a
+        # .config file or via set_value(), and are never implicitly updated).
+        if value == self.user_value and not self.choice:
+            self._was_set = True
+            return True
+
+        # Check if the value is valid for our type
+        if not (self.orig_type is BOOL     and value in (2, 0)     or
+                self.orig_type is TRISTATE and value in TRI_TO_STR or
+                value.__class__ is str and
+                (self.orig_type is STRING                        or
+                 self.orig_type is INT and _is_base_n(value, 10) or
+                 self.orig_type is HEX and _is_base_n(value, 16)
+                                       and int(value, 16) >= 0)):
+
+            # Display tristate values as n, m, y in the warning
+            self.kconfig._warn(
+                "the value {} is invalid for {}, which has type {} -- "
+                "assignment ignored"
+                .format(TRI_TO_STR[value] if value in TRI_TO_STR else
+                            "'{}'".format(value),
+                        self.name_and_loc, TYPE_TO_STR[self.orig_type]))
+
+            return False
+
+        self.user_value = value
+        self._was_set = True
+
+        if self.choice and value == 2:
+            # Setting a choice symbol to y makes it the user selection of the
+            # choice. Like for symbol user values, the user selection is not
+            # guaranteed to match the actual selection of the choice, as
+            # dependencies come into play.
+            self.choice.user_selection = self
+            self.choice._was_set = True
+            self.choice._rec_invalidate()
+        else:
+            self._rec_invalidate_if_has_prompt()
+
+        return True
+
+    def unset_value(self):
+        """
+        Removes any user value from the symbol, as if the symbol had never
+        gotten a user value via Kconfig.load_config() or Symbol.set_value().
+        """
+        if self.user_value is not None:
+            self.user_value = None
+            self._rec_invalidate_if_has_prompt()
+
+    @property
+    def referenced(self):
+        """
+        See the class documentation.
+        """
+        return {item for node in self.nodes for item in node.referenced}
+
+    @property
+    def orig_defaults(self):
+        """
+        See the class documentation.
+        """
+        return [d for node in self.nodes for d in node.orig_defaults]
+
+    @property
+    def orig_selects(self):
+        """
+        See the class documentation.
+        """
+        return [s for node in self.nodes for s in node.orig_selects]
+
+    @property
+    def orig_implies(self):
+        """
+        See the class documentation.
+        """
+        return [i for node in self.nodes for i in node.orig_implies]
+
+    @property
+    def orig_ranges(self):
+        """
+        See the class documentation.
+        """
+        return [r for node in self.nodes for r in node.orig_ranges]
+
+    def __repr__(self):
+        """
+        Returns a string with information about the symbol (including its name,
+        value, visibility, and location(s)) when it is evaluated on e.g. the
+        interactive Python prompt.
+        """
+        fields = ["symbol " + self.name, TYPE_TO_STR[self.type]]
+        add = fields.append
+
+        for node in self.nodes:
+            if node.prompt:
+                add('"{}"'.format(node.prompt[0]))
+
+        # Only add quotes for non-bool/tristate symbols
+        add("value " + (self.str_value if self.orig_type in _BOOL_TRISTATE
+                        else '"{}"'.format(self.str_value)))
+
+        if not self.is_constant:
+            # These aren't helpful to show for constant symbols
+
+            if self.user_value is not None:
+                # Only add quotes for non-bool/tristate symbols
+                add("user value " + (TRI_TO_STR[self.user_value]
+                                     if self.orig_type in _BOOL_TRISTATE
+                                     else '"{}"'.format(self.user_value)))
+
+            add("visibility " + TRI_TO_STR[self.visibility])
+
+            if self.choice:
+                add("choice symbol")
+
+            if self.is_allnoconfig_y:
+                add("allnoconfig_y")
+
+            if self is self.kconfig.defconfig_list:
+                add("is the defconfig_list symbol")
+
+            if self.env_var is not None:
+                add("from environment variable " + self.env_var)
+
+            if self is self.kconfig.modules:
+                add("is the modules symbol")
+
+            add("direct deps " + TRI_TO_STR[expr_value(self.direct_dep)])
+
+        if self.nodes:
+            for node in self.nodes:
+                add("{}:{}".format(node.filename, node.linenr))
+        else:
+            add("constant" if self.is_constant else "undefined")
+
+        return "<{}>".format(", ".join(fields))
+
+    def __str__(self):
+        """
+        Returns a string representation of the symbol when it is printed.
+        Matches the Kconfig format, with any parent dependencies propagated to
+        the 'depends on' condition.
+
+        The string is constructed by joining the strings returned by
+        MenuNode.__str__() for each of the symbol's menu nodes, so symbols
+        defined in multiple locations will return a string with all
+        definitions.
+
+        The returned string does not end in a newline. An empty string is
+        returned for undefined and constant symbols.
+        """
+        return self.custom_str(standard_sc_expr_str)
+
+    def custom_str(self, sc_expr_str_fn):
+        """
+        Works like Symbol.__str__(), but allows a custom format to be used for
+        all symbol/choice references. See expr_str().
+        """
+        return "\n\n".join(node.custom_str(sc_expr_str_fn)
+                           for node in self.nodes)
+
+    #
+    # Private methods
+    #
+
+    def __init__(self):
+        """
+        Symbol constructor -- not intended to be called directly by Kconfiglib
+        clients.
+        """
+        # These attributes are always set on the instance from outside and
+        # don't need defaults:
+        #   kconfig
+        #   direct_dep
+        #   is_constant
+        #   name
+        #   rev_dep
+        #   weak_rev_dep
+
+        # - UNKNOWN == 0
+        # - _visited is used during tree iteration and dep. loop detection
+        self.orig_type = self._visited = 0
+
+        self.nodes = []
+
+        self.defaults = []
+        self.selects = []
+        self.implies = []
+        self.ranges = []
+
+        self.user_value = \
+        self.choice = \
+        self.env_var = \
+        self._cached_str_val = self._cached_tri_val = self._cached_vis = \
+        self._cached_assignable = None
+
+        # _write_to_conf is calculated along with the value. If True, the
+        # Symbol gets a .config entry.
+
+        self.is_allnoconfig_y = \
+        self._was_set = \
+        self._write_to_conf = False
+
+        # See Kconfig._build_dep()
+        self._dependents = set()
+
+    def _assignable(self):
+        # Worker function for the 'assignable' attribute
+
+        if self.orig_type not in _BOOL_TRISTATE:
+            return ()
+
+        # Warning: See Symbol._rec_invalidate(), and note that this is a hidden
+        # function call (property magic)
+        vis = self.visibility
+        if not vis:
+            return ()
+
+        rev_dep_val = expr_value(self.rev_dep)
+
+        if vis == 2:
+            if self.choice:
+                return (2,)
+
+            if not rev_dep_val:
+                if self.type is BOOL or expr_value(self.weak_rev_dep) == 2:
+                    return (0, 2)
+                return (0, 1, 2)
+
+            if rev_dep_val == 2:
+                return (2,)
+
+            # rev_dep_val == 1
+
+            if self.type is BOOL or expr_value(self.weak_rev_dep) == 2:
+                return (2,)
+            return (1, 2)
+
+        # vis == 1
+
+        # Must be a tristate here, because bool m visibility gets promoted to y
+
+        if not rev_dep_val:
+            return (0, 1) if expr_value(self.weak_rev_dep) != 2 else (0, 2)
+
+        if rev_dep_val == 2:
+            return (2,)
+
+        # vis == rev_dep_val == 1
+
+        return (1,)
+
+    def _invalidate(self):
+        # Marks the symbol as needing to be recalculated
+
+        self._cached_str_val = self._cached_tri_val = self._cached_vis = \
+        self._cached_assignable = None
+
+    def _rec_invalidate(self):
+        # Invalidates the symbol and all items that (possibly) depend on it
+
+        if self is self.kconfig.modules:
+            # Invalidating MODULES has wide-ranging effects
+            self.kconfig._invalidate_all()
+        else:
+            self._invalidate()
+
+            for item in self._dependents:
+                # _cached_vis doubles as a flag that tells us whether 'item'
+                # has cached values, because it's calculated as a side effect
+                # of calculating all other (non-constant) cached values.
+                #
+                # If item._cached_vis is None, it means there can't be cached
+                # values on other items that depend on 'item', because if there
+                # were, some value on 'item' would have been calculated and
+                # item._cached_vis set as a side effect. It's therefore safe to
+                # stop the invalidation at symbols with _cached_vis None.
+                #
+                # This approach massively speeds up scripts that set a lot of
+                # values, vs simply invalidating all possibly dependent symbols
+                # (even when you already have a list of all the dependent
+                # symbols, because some symbols get huge dependency trees).
+                #
+                # This gracefully handles dependency loops too, which is nice
+                # for choices, where the choice depends on the choice symbols
+                # and vice versa.
+                if item._cached_vis is not None:
+                    item._rec_invalidate()
+
+    def _rec_invalidate_if_has_prompt(self):
+        # Invalidates the symbol and its dependent symbols, but only if the
+        # symbol has a prompt. User values never have an effect on promptless
+        # symbols, so we skip invalidation for them as an optimization.
+        #
+        # This also prevents constant (quoted) symbols from being invalidated
+        # if set_value() is called on them, which would make them lose their
+        # value and break things.
+        #
+        # Prints a warning if the symbol has no prompt. In some contexts (e.g.
+        # when loading a .config files) assignments to promptless symbols are
+        # normal and expected, so the warning can be disabled.
+
+        for node in self.nodes:
+            if node.prompt:
+                self._rec_invalidate()
+                return
+
+        if self.kconfig._warn_assign_no_prompt:
+            self.kconfig._warn(self.name_and_loc + " has no prompt, meaning "
+                               "user values have no effect on it")
+
+    def _str_default(self):
+        # write_min_config() helper function. Returns the value the symbol
+        # would get from defaults if it didn't have a user value. Uses exactly
+        # the same algorithm as the C implementation (though a bit cleaned up),
+        # for compatibility.
+
+        if self.orig_type in _BOOL_TRISTATE:
+            val = 0
+
+            # Defaults, selects, and implies do not affect choice symbols
+            if not self.choice:
+                for default, cond in self.defaults:
+                    cond_val = expr_value(cond)
+                    if cond_val:
+                        val = min(expr_value(default), cond_val)
+                        break
+
+                val = max(expr_value(self.rev_dep),
+                          expr_value(self.weak_rev_dep),
+                          val)
+
+                # Transpose mod to yes if type is bool (possibly due to modules
+                # being disabled)
+                if val == 1 and self.type is BOOL:
+                    val = 2
+
+            return TRI_TO_STR[val]
+
+        if self.orig_type:  # STRING/INT/HEX
+            for default, cond in self.defaults:
+                if expr_value(cond):
+                    return default.str_value
+
+        return ""
+
+    def _warn_select_unsatisfied_deps(self):
+        # Helper for printing an informative warning when a symbol with
+        # unsatisfied direct dependencies (dependencies from 'depends on', ifs,
+        # and menus) is selected by some other symbol. Also warn if a symbol
+        # whose direct dependencies evaluate to m is selected to y.
+
+        msg = "{} has direct dependencies {} with value {}, but is " \
+              "currently being {}-selected by the following symbols:" \
+              .format(self.name_and_loc, expr_str(self.direct_dep),
+                      TRI_TO_STR[expr_value(self.direct_dep)],
+                      TRI_TO_STR[expr_value(self.rev_dep)])
+
+        # The reverse dependencies from each select are ORed together
+        for select in split_expr(self.rev_dep, OR):
+            if expr_value(select) <= expr_value(self.direct_dep):
+                # Only include selects that exceed the direct dependencies
+                continue
+
+            # - 'select A if B' turns into A && B
+            # - 'select A' just turns into A
+            #
+            # In both cases, we can split on AND and pick the first operand
+            selecting_sym = split_expr(select, AND)[0]
+
+            msg += "\n - {}, with value {}, direct dependencies {} " \
+                   "(value: {})" \
+                   .format(selecting_sym.name_and_loc,
+                           selecting_sym.str_value,
+                           expr_str(selecting_sym.direct_dep),
+                           TRI_TO_STR[expr_value(selecting_sym.direct_dep)])
+
+            if select.__class__ is tuple:
+                msg += ", and select condition {} (value: {})" \
+                       .format(expr_str(select[2]),
+                               TRI_TO_STR[expr_value(select[2])])
+
+        self.kconfig._warn(msg)
+
+
+class Choice(object):
+    """
+    Represents a choice statement:
+
+      choice
+          ...
+      endchoice
+
+    The following attributes are available on Choice instances. They should be
+    treated as read-only, and some are implemented through @property magic (but
+    are still efficient to access due to internal caching).
+
+    Note: Prompts, help texts, and locations are stored in the Choice's
+    MenuNode(s) rather than in the Choice itself. Check the MenuNode class and
+    the Choice.nodes attribute. This organization matches the C tools.
+
+    name:
+      The name of the choice, e.g. "FOO" for 'choice FOO', or None if the
+      Choice has no name.
+
+    type:
+      The type of the choice. One of BOOL, TRISTATE, UNKNOWN. UNKNOWN is for
+      choices defined without a type where none of the contained symbols have a
+      type either (otherwise the choice inherits the type of the first symbol
+      defined with a type).
+
+      When running without modules (CONFIG_MODULES=n), TRISTATE choices
+      magically change type to BOOL. This matches the C tools, and makes sense
+      for menuconfig-like functionality.
+
+    orig_type:
+      The type as given in the Kconfig file, without any magic applied. Used
+      when printing the choice.
+
+    tri_value:
+      The tristate value (mode) of the choice. A choice can be in one of three
+      modes:
+
+        0 (n) - The choice is disabled and no symbols can be selected. For
+                visible choices, this mode is only possible for choices with
+                the 'optional' flag set (see kconfig-language.txt).
+
+        1 (m) - Any number of choice symbols can be set to m, the rest will
+                be n.
+
+        2 (y) - One symbol will be y, the rest n.
+
+      Only tristate choices can be in m mode. The visibility of the choice is
+      an upper bound on the mode, and the mode in turn is an upper bound on the
+      visibility of the choice symbols.
+
+      To change the mode, use Choice.set_value().
+
+      Implementation note:
+        The C tools internally represent choices as a type of symbol, with
+        special-casing in many code paths. This is why there is a lot of
+        similarity to Symbol. The value (mode) of a choice is really just a
+        normal symbol value, and an implicit reverse dependency forces its
+        lower bound to m for visible non-optional choices (the reverse
+        dependency is 'm && <visibility>').
+
+        Symbols within choices get the choice propagated as a dependency to
+        their properties. This turns the mode of the choice into an upper bound
+        on e.g. the visibility of choice symbols, and explains the gotcha
+        related to printing choice symbols mentioned in the module docstring.
+
+        Kconfiglib uses a separate Choice class only because it makes the code
+        and interface less confusing (especially in a user-facing interface).
+        Corresponding attributes have the same name in the Symbol and Choice
+        classes, for consistency and compatibility.
+
+    str_value:
+      Like choice.tri_value, but gives the value as one of the strings
+      "n", "m", or "y"
+
+    user_value:
+      The value (mode) selected by the user through Choice.set_value(). Either
+      0, 1, or 2, or None if the user hasn't selected a mode. See
+      Symbol.user_value.
+
+      WARNING: Do not assign directly to this. It will break things. Use
+      Choice.set_value() instead.
+
+    assignable:
+      See the symbol class documentation. Gives the assignable values (modes).
+
+    selection:
+      The Symbol instance of the currently selected symbol. None if the Choice
+      is not in y mode or has no selected symbol (due to unsatisfied
+      dependencies on choice symbols).
+
+      WARNING: Do not assign directly to this. It will break things. Call
+      sym.set_value(2) on the choice symbol you want to select instead.
+
+    user_selection:
+      The symbol selected by the user (by setting it to y). Ignored if the
+      choice is not in y mode, but still remembered so that the choice "snaps
+      back" to the user selection if the mode is changed back to y. This might
+      differ from 'selection' due to unsatisfied dependencies.
+
+      WARNING: Do not assign directly to this. It will break things. Call
+      sym.set_value(2) on the choice symbol to be selected instead.
+
+    visibility:
+      See the Symbol class documentation. Acts on the value (mode).
+
+    name_and_loc:
+      Holds a string like
+
+        "<choice MY_CHOICE> (defined at foo/Kconfig:12)"
+
+      , giving the name of the choice and its definition location(s). If the
+      choice has no name (isn't defined with 'choice MY_CHOICE'), then it will
+      be shown as "<choice>" before the list of locations (always a single one
+      in that case).
+
+    syms:
+      List of symbols contained in the choice.
+
+      Obscure gotcha: If a symbol depends on the previous symbol within a
+      choice so that an implicit menu is created, it won't be a choice symbol,
+      and won't be included in 'syms'.
+
+    nodes:
+      A list of MenuNodes for this choice. In practice, the list will probably
+      always contain a single MenuNode, but it is possible to give a choice a
+      name and define it in multiple locations.
+
+    defaults:
+      List of (symbol, cond) tuples for the choice's 'defaults' properties. For
+      example, 'default A if B && C' is represented as (A, (AND, B, C)). If
+      there is no condition, 'cond' is self.kconfig.y.
+
+      Note that 'depends on' and parent dependencies are propagated to
+      'default' conditions.
+
+    orig_defaults:
+      See the corresponding attribute on the MenuNode class.
+
+    direct_dep:
+      See Symbol.direct_dep.
+
+    referenced:
+      A set() with all symbols referenced in the properties and property
+      conditions of the choice.
+
+      Also includes dependencies from surrounding menus and ifs, because those
+      get propagated to the choice (see the 'Intro to symbol values' section in
+      the module docstring).
+
+    is_optional:
+      True if the choice has the 'optional' flag set on it and can be in
+      n mode.
+
+    kconfig:
+      The Kconfig instance this choice is from.
+    """
+    __slots__ = (
+        "_cached_assignable",
+        "_cached_selection",
+        "_cached_vis",
+        "_dependents",
+        "_visited",
+        "_was_set",
+        "defaults",
+        "direct_dep",
+        "is_constant",
+        "is_optional",
+        "kconfig",
+        "name",
+        "nodes",
+        "orig_type",
+        "syms",
+        "user_selection",
+        "user_value",
+    )
+
+    #
+    # Public interface
+    #
+
+    @property
+    def type(self):
+        """
+        Returns the type of the choice. See Symbol.type.
+        """
+        if self.orig_type is TRISTATE and not self.kconfig.modules.tri_value:
+            return BOOL
+        return self.orig_type
+
+    @property
+    def str_value(self):
+        """
+        See the class documentation.
+        """
+        return TRI_TO_STR[self.tri_value]
+
+    @property
+    def tri_value(self):
+        """
+        See the class documentation.
+        """
+        # This emulates a reverse dependency of 'm && visibility' for
+        # non-optional choices, which is how the C implementation does it
+
+        val = 0 if self.is_optional else 1
+
+        if self.user_value is not None:
+            val = max(val, self.user_value)
+
+        # Warning: See Symbol._rec_invalidate(), and note that this is a hidden
+        # function call (property magic)
+        val = min(val, self.visibility)
+
+        # Promote m to y for boolean choices
+        return 2 if val == 1 and self.type is BOOL else val
+
+    @property
+    def assignable(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_assignable is None:
+            self._cached_assignable = self._assignable()
+        return self._cached_assignable
+
+    @property
+    def visibility(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_vis is None:
+            self._cached_vis = _visibility(self)
+        return self._cached_vis
+
+    @property
+    def name_and_loc(self):
+        """
+        See the class documentation.
+        """
+        # Reuse the expression format, which is '<choice (name, if any)>'.
+        return standard_sc_expr_str(self) + " " + _locs(self)
+
+    @property
+    def selection(self):
+        """
+        See the class documentation.
+        """
+        if self._cached_selection is _NO_CACHED_SELECTION:
+            self._cached_selection = self._selection()
+        return self._cached_selection
+
+    def set_value(self, value):
+        """
+        Sets the user value (mode) of the choice. Like for Symbol.set_value(),
+        the visibility might truncate the value. Choices without the 'optional'
+        attribute (is_optional) can never be in n mode, but 0/"n" is still
+        accepted since it's not a malformed value (though it will have no
+        effect).
+
+        Returns True if the value is valid for the type of the choice, and
+        False otherwise. This only looks at the form of the value. Check the
+        Choice.assignable attribute to see what values are currently in range
+        and would actually be reflected in the mode of the choice.
+        """
+        if value in STR_TO_TRI:
+            value = STR_TO_TRI[value]
+
+        if value == self.user_value:
+            # We know the value must be valid if it was successfully set
+            # previously
+            self._was_set = True
+            return True
+
+        if not (self.orig_type is BOOL     and value in (2, 0) or
+                self.orig_type is TRISTATE and value in TRI_TO_STR):
+
+            # Display tristate values as n, m, y in the warning
+            self.kconfig._warn(
+                "the value {} is invalid for {}, which has type {} -- "
+                "assignment ignored"
+                .format(TRI_TO_STR[value] if value in TRI_TO_STR else
+                            "'{}'".format(value),
+                        self.name_and_loc, TYPE_TO_STR[self.orig_type]))
+
+            return False
+
+        self.user_value = value
+        self._was_set = True
+        self._rec_invalidate()
+
+        return True
+
+    def unset_value(self):
+        """
+        Resets the user value (mode) and user selection of the Choice, as if
+        the user had never touched the mode or any of the choice symbols.
+        """
+        if self.user_value is not None or self.user_selection:
+            self.user_value = self.user_selection = None
+            self._rec_invalidate()
+
+    @property
+    def referenced(self):
+        """
+        See the class documentation.
+        """
+        return {item for node in self.nodes for item in node.referenced}
+
+    @property
+    def orig_defaults(self):
+        """
+        See the class documentation.
+        """
+        return [d for node in self.nodes for d in node.orig_defaults]
+
+    def __repr__(self):
+        """
+        Returns a string with information about the choice when it is evaluated
+        on e.g. the interactive Python prompt.
+        """
+        fields = ["choice " + self.name if self.name else "choice",
+                  TYPE_TO_STR[self.type]]
+        add = fields.append
+
+        for node in self.nodes:
+            if node.prompt:
+                add('"{}"'.format(node.prompt[0]))
+
+        add("mode " + self.str_value)
+
+        if self.user_value is not None:
+            add('user mode {}'.format(TRI_TO_STR[self.user_value]))
+
+        if self.selection:
+            add("{} selected".format(self.selection.name))
+
+        if self.user_selection:
+            user_sel_str = "{} selected by user" \
+                           .format(self.user_selection.name)
+
+            if self.selection is not self.user_selection:
+                user_sel_str += " (overridden)"
+
+            add(user_sel_str)
+
+        add("visibility " + TRI_TO_STR[self.visibility])
+
+        if self.is_optional:
+            add("optional")
+
+        for node in self.nodes:
+            add("{}:{}".format(node.filename, node.linenr))
+
+        return "<{}>".format(", ".join(fields))
+
+    def __str__(self):
+        """
+        Returns a string representation of the choice when it is printed.
+        Matches the Kconfig format (though without the contained choice
+        symbols), with any parent dependencies propagated to the 'depends on'
+        condition.
+
+        The returned string does not end in a newline.
+
+        See Symbol.__str__() as well.
+        """
+        return self.custom_str(standard_sc_expr_str)
+
+    def custom_str(self, sc_expr_str_fn):
+        """
+        Works like Choice.__str__(), but allows a custom format to be used for
+        all symbol/choice references. See expr_str().
+        """
+        return "\n\n".join(node.custom_str(sc_expr_str_fn)
+                           for node in self.nodes)
+
+    #
+    # Private methods
+    #
+
+    def __init__(self):
+        """
+        Choice constructor -- not intended to be called directly by Kconfiglib
+        clients.
+        """
+        # These attributes are always set on the instance from outside and
+        # don't need defaults:
+        #   direct_dep
+        #   kconfig
+
+        # - UNKNOWN == 0
+        # - _visited is used during dep. loop detection
+        self.orig_type = self._visited = 0
+
+        self.nodes = []
+
+        self.syms = []
+        self.defaults = []
+
+        self.name = \
+        self.user_value = self.user_selection = \
+        self._cached_vis = self._cached_assignable = None
+
+        self._cached_selection = _NO_CACHED_SELECTION
+
+        # is_constant is checked by _depend_on(). Just set it to avoid having
+        # to special-case choices.
+        self.is_constant = self.is_optional = False
+
+        # See Kconfig._build_dep()
+        self._dependents = set()
+
+    def _assignable(self):
+        # Worker function for the 'assignable' attribute
+
+        # Warning: See Symbol._rec_invalidate(), and note that this is a hidden
+        # function call (property magic)
+        vis = self.visibility
+
+        if not vis:
+            return ()
+
+        if vis == 2:
+            if not self.is_optional:
+                return (2,) if self.type is BOOL else (1, 2)
+            return (0, 2) if self.type is BOOL else (0, 1, 2)
+
+        # vis == 1
+
+        return (0, 1) if self.is_optional else (1,)
+
+    def _selection(self):
+        # Worker function for the 'selection' attribute
+
+        # Warning: See Symbol._rec_invalidate(), and note that this is a hidden
+        # function call (property magic)
+        if self.tri_value != 2:
+            # Not in y mode, so no selection
+            return None
+
+        # Use the user selection if it's visible
+        if self.user_selection and self.user_selection.visibility:
+            return self.user_selection
+
+        # Otherwise, check if we have a default
+        return self._selection_from_defaults()
+
+    def _selection_from_defaults(self):
+        # Check if we have a default
+        for sym, cond in self.defaults:
+            # The default symbol must be visible too
+            if expr_value(cond) and sym.visibility:
+                return sym
+
+        # Otherwise, pick the first visible symbol, if any
+        for sym in self.syms:
+            if sym.visibility:
+                return sym
+
+        # Couldn't find a selection
+        return None
+
+    def _invalidate(self):
+        self._cached_vis = self._cached_assignable = None
+        self._cached_selection = _NO_CACHED_SELECTION
+
+    def _rec_invalidate(self):
+        # See Symbol._rec_invalidate()
+
+        self._invalidate()
+
+        for item in self._dependents:
+            if item._cached_vis is not None:
+                item._rec_invalidate()
+
+
+class MenuNode(object):
+    """
+    Represents a menu node in the configuration. This corresponds to an entry
+    in e.g. the 'make menuconfig' interface, though non-visible choices, menus,
+    and comments also get menu nodes. If a symbol or choice is defined in
+    multiple locations, it gets one menu node for each location.
+
+    The top-level menu node, corresponding to the implicit top-level menu, is
+    available in Kconfig.top_node.
+
+    The menu nodes for a Symbol or Choice can be found in the
+    Symbol/Choice.nodes attribute. Menus and comments are represented as plain
+    menu nodes, with their text stored in the prompt attribute (prompt[0]).
+    This mirrors the C implementation.
+
+    The following attributes are available on MenuNode instances. They should
+    be viewed as read-only.
+
+    item:
+      Either a Symbol, a Choice, or one of the constants MENU and COMMENT.
+      Menus and comments are represented as plain menu nodes. Ifs are collapsed
+      (matching the C implementation) and do not appear in the final menu tree.
+
+    next:
+      The following menu node. None if there is no following node.
+
+    list:
+      The first child menu node. None if there are no children.
+
+      Choices and menus naturally have children, but Symbols can also have
+      children because of menus created automatically from dependencies (see
+      kconfig-language.txt).
+
+    parent:
+      The parent menu node. None if there is no parent.
+
+    prompt:
+      A (string, cond) tuple with the prompt for the menu node and its
+      conditional expression (which is self.kconfig.y if there is no
+      condition). None if there is no prompt.
+
+      For symbols and choices, the prompt is stored in the MenuNode rather than
+      the Symbol or Choice instance. For menus and comments, the prompt holds
+      the text.
+
+    defaults:
+      The 'default' properties for this particular menu node. See
+      symbol.defaults.
+
+      When evaluating defaults, you should use Symbol/Choice.defaults instead,
+      as it include properties from all menu nodes (a symbol/choice can have
+      multiple definition locations/menu nodes). MenuNode.defaults is meant for
+      documentation generation.
+
+    selects:
+      Like MenuNode.defaults, for selects.
+
+    implies:
+      Like MenuNode.defaults, for implies.
+
+    ranges:
+      Like MenuNode.defaults, for ranges.
+
+    orig_prompt:
+    orig_defaults:
+    orig_selects:
+    orig_implies:
+    orig_ranges:
+      These work the like the corresponding attributes without orig_*, but omit
+      any dependencies propagated from 'depends on' and surrounding 'if's (the
+      direct dependencies, stored in MenuNode.dep).
+
+      One use for this is generating less cluttered documentation, by only
+      showing the direct dependencies in one place.
+
+    help:
+      The help text for the menu node for Symbols and Choices. None if there is
+      no help text. Always stored in the node rather than the Symbol or Choice.
+      It is possible to have a separate help text at each location if a symbol
+      is defined in multiple locations.
+
+      Trailing whitespace (including a final newline) is stripped from the help
+      text. This was not the case before Kconfiglib 10.21.0, where the format
+      was undocumented.
+
+    dep:
+      The direct ('depends on') dependencies for the menu node, or
+      self.kconfig.y if there are no direct dependencies.
+
+      This attribute includes any dependencies from surrounding menus and ifs.
+      Those get propagated to the direct dependencies, and the resulting direct
+      dependencies in turn get propagated to the conditions of all properties.
+
+      If a symbol or choice is defined in multiple locations, only the
+      properties defined at a particular location get the corresponding
+      MenuNode.dep dependencies propagated to them.
+
+    visibility:
+      The 'visible if' dependencies for the menu node (which must represent a
+      menu), or self.kconfig.y if there are no 'visible if' dependencies.
+      'visible if' dependencies are recursively propagated to the prompts of
+      symbols and choices within the menu.
+
+    referenced:
+      A set() with all symbols and choices referenced in the properties and
+      property conditions of the menu node.
+
+      Also includes dependencies inherited from surrounding menus and ifs.
+      Choices appear in the dependencies of choice symbols.
+
+    is_menuconfig:
+      Set to True if the children of the menu node should be displayed in a
+      separate menu. This is the case for the following items:
+
+        - Menus (node.item == MENU)
+
+        - Choices
+
+        - Symbols defined with the 'menuconfig' keyword. The children come from
+          implicitly created submenus, and should be displayed in a separate
+          menu rather than being indented.
+
+      'is_menuconfig' is just a hint on how to display the menu node. It's
+      ignored internally by Kconfiglib, except when printing symbols.
+
+    filename/linenr:
+      The location where the menu node appears. The filename is relative to
+      $srctree (or to the current directory if $srctree isn't set), except
+      absolute paths are used for paths outside $srctree.
+
+    include_path:
+      A tuple of (filename, linenr) tuples, giving the locations of the
+      'source' statements via which the Kconfig file containing this menu node
+      was included. The first element is the location of the 'source' statement
+      in the top-level Kconfig file passed to Kconfig.__init__(), etc.
+
+      Note that the Kconfig file of the menu node itself isn't included. Check
+      'filename' and 'linenr' for that.
+
+    kconfig:
+      The Kconfig instance the menu node is from.
+    """
+    __slots__ = (
+        "dep",
+        "filename",
+        "help",
+        "include_path",
+        "is_menuconfig",
+        "item",
+        "kconfig",
+        "linenr",
+        "list",
+        "next",
+        "parent",
+        "prompt",
+        "visibility",
+
+        # Properties
+        "defaults",
+        "selects",
+        "implies",
+        "ranges",
+    )
+
+    def __init__(self):
+        # Properties defined on this particular menu node. A local 'depends on'
+        # only applies to these, in case a symbol is defined in multiple
+        # locations.
+        self.defaults = []
+        self.selects = []
+        self.implies = []
+        self.ranges = []
+
+    @property
+    def orig_prompt(self):
+        """
+        See the class documentation.
+        """
+        if not self.prompt:
+            return None
+        return (self.prompt[0], self._strip_dep(self.prompt[1]))
+
+    @property
+    def orig_defaults(self):
+        """
+        See the class documentation.
+        """
+        return [(default, self._strip_dep(cond))
+                for default, cond in self.defaults]
+
+    @property
+    def orig_selects(self):
+        """
+        See the class documentation.
+        """
+        return [(select, self._strip_dep(cond))
+                for select, cond in self.selects]
+
+    @property
+    def orig_implies(self):
+        """
+        See the class documentation.
+        """
+        return [(imply, self._strip_dep(cond))
+                for imply, cond in self.implies]
+
+    @property
+    def orig_ranges(self):
+        """
+        See the class documentation.
+        """
+        return [(low, high, self._strip_dep(cond))
+                for low, high, cond in self.ranges]
+
+    @property
+    def referenced(self):
+        """
+        See the class documentation.
+        """
+        # self.dep is included to catch dependencies from a lone 'depends on'
+        # when there are no properties to propagate it to
+        res = expr_items(self.dep)
+
+        if self.prompt:
+            res |= expr_items(self.prompt[1])
+
+        if self.item is MENU:
+            res |= expr_items(self.visibility)
+
+        for value, cond in self.defaults:
+            res |= expr_items(value)
+            res |= expr_items(cond)
+
+        for value, cond in self.selects:
+            res.add(value)
+            res |= expr_items(cond)
+
+        for value, cond in self.implies:
+            res.add(value)
+            res |= expr_items(cond)
+
+        for low, high, cond in self.ranges:
+            res.add(low)
+            res.add(high)
+            res |= expr_items(cond)
+
+        return res
+
+    def __repr__(self):
+        """
+        Returns a string with information about the menu node when it is
+        evaluated on e.g. the interactive Python prompt.
+        """
+        fields = []
+        add = fields.append
+
+        if self.item.__class__ is Symbol:
+            add("menu node for symbol " + self.item.name)
+
+        elif self.item.__class__ is Choice:
+            s = "menu node for choice"
+            if self.item.name is not None:
+                s += " " + self.item.name
+            add(s)
+
+        elif self.item is MENU:
+            add("menu node for menu")
+
+        else:  # self.item is COMMENT
+            add("menu node for comment")
+
+        if self.prompt:
+            add('prompt "{}" (visibility {})'.format(
+                self.prompt[0], TRI_TO_STR[expr_value(self.prompt[1])]))
+
+        if self.item.__class__ is Symbol and self.is_menuconfig:
+            add("is menuconfig")
+
+        add("deps " + TRI_TO_STR[expr_value(self.dep)])
+
+        if self.item is MENU:
+            add("'visible if' deps " + TRI_TO_STR[expr_value(self.visibility)])
+
+        if self.item.__class__ in _SYMBOL_CHOICE and self.help is not None:
+            add("has help")
+
+        if self.list:
+            add("has child")
+
+        if self.next:
+            add("has next")
+
+        add("{}:{}".format(self.filename, self.linenr))
+
+        return "<{}>".format(", ".join(fields))
+
+    def __str__(self):
+        """
+        Returns a string representation of the menu node. Matches the Kconfig
+        format, with any parent dependencies propagated to the 'depends on'
+        condition.
+
+        The output could (almost) be fed back into a Kconfig parser to redefine
+        the object associated with the menu node. See the module documentation
+        for a gotcha related to choice symbols.
+
+        For symbols and choices with multiple menu nodes (multiple definition
+        locations), properties that aren't associated with a particular menu
+        node are shown on all menu nodes ('option env=...', 'optional' for
+        choices, etc.).
+
+        The returned string does not end in a newline.
+        """
+        return self.custom_str(standard_sc_expr_str)
+
+    def custom_str(self, sc_expr_str_fn):
+        """
+        Works like MenuNode.__str__(), but allows a custom format to be used
+        for all symbol/choice references. See expr_str().
+        """
+        return self._menu_comment_node_str(sc_expr_str_fn) \
+               if self.item in _MENU_COMMENT else \
+               self._sym_choice_node_str(sc_expr_str_fn)
+
+    def _menu_comment_node_str(self, sc_expr_str_fn):
+        s = '{} "{}"'.format("menu" if self.item is MENU else "comment",
+                             self.prompt[0])
+
+        if self.dep is not self.kconfig.y:
+            s += "\n\tdepends on {}".format(expr_str(self.dep, sc_expr_str_fn))
+
+        if self.item is MENU and self.visibility is not self.kconfig.y:
+            s += "\n\tvisible if {}".format(expr_str(self.visibility,
+                                                     sc_expr_str_fn))
+
+        return s
+
+    def _sym_choice_node_str(self, sc_expr_str_fn):
+        def indent_add(s):
+            lines.append("\t" + s)
+
+        def indent_add_cond(s, cond):
+            if cond is not self.kconfig.y:
+                s += " if " + expr_str(cond, sc_expr_str_fn)
+            indent_add(s)
+
+        sc = self.item
+
+        if sc.__class__ is Symbol:
+            lines = [("menuconfig " if self.is_menuconfig else "config ")
+                     + sc.name]
+        else:
+            lines = ["choice " + sc.name if sc.name else "choice"]
+
+        if sc.orig_type and not self.prompt:  # sc.orig_type != UNKNOWN
+            # If there's a prompt, we'll use the '<type> "prompt"' shorthand
+            # instead
+            indent_add(TYPE_TO_STR[sc.orig_type])
+
+        if self.prompt:
+            if sc.orig_type:
+                prefix = TYPE_TO_STR[sc.orig_type]
+            else:
+                # Symbol defined without a type (which generates a warning)
+                prefix = "prompt"
+
+            indent_add_cond(prefix + ' "{}"'.format(escape(self.prompt[0])),
+                            self.orig_prompt[1])
+
+        if sc.__class__ is Symbol:
+            if sc.is_allnoconfig_y:
+                indent_add("option allnoconfig_y")
+
+            if sc is sc.kconfig.defconfig_list:
+                indent_add("option defconfig_list")
+
+            if sc.env_var is not None:
+                indent_add('option env="{}"'.format(sc.env_var))
+
+            if sc is sc.kconfig.modules:
+                indent_add("option modules")
+
+            for low, high, cond in self.orig_ranges:
+                indent_add_cond(
+                    "range {} {}".format(sc_expr_str_fn(low),
+                                         sc_expr_str_fn(high)),
+                    cond)
+
+        for default, cond in self.orig_defaults:
+            indent_add_cond("default " + expr_str(default, sc_expr_str_fn),
+                            cond)
+
+        if sc.__class__ is Choice and sc.is_optional:
+            indent_add("optional")
+
+        if sc.__class__ is Symbol:
+            for select, cond in self.orig_selects:
+                indent_add_cond("select " + sc_expr_str_fn(select), cond)
+
+            for imply, cond in self.orig_implies:
+                indent_add_cond("imply " + sc_expr_str_fn(imply), cond)
+
+        if self.dep is not sc.kconfig.y:
+            indent_add("depends on " + expr_str(self.dep, sc_expr_str_fn))
+
+        if self.help is not None:
+            indent_add("help")
+            for line in self.help.splitlines():
+                indent_add("  " + line)
+
+        return "\n".join(lines)
+
+    def _strip_dep(self, expr):
+        # Helper function for removing MenuNode.dep from 'expr'. Uses two
+        # pieces of internal knowledge: (1) Expressions are reused rather than
+        # copied, and (2) the direct dependencies always appear at the end.
+
+        # ... if dep -> ... if y
+        if self.dep is expr:
+            return self.kconfig.y
+
+        # (AND, X, dep) -> X
+        if expr.__class__ is tuple and expr[0] is AND and expr[2] is self.dep:
+            return expr[1]
+
+        return expr
+
+
+class Variable(object):
+    """
+    Represents a preprocessor variable/function.
+
+    The following attributes are available:
+
+    name:
+      The name of the variable.
+
+    value:
+      The unexpanded value of the variable.
+
+    expanded_value:
+      The expanded value of the variable. For simple variables (those defined
+      with :=), this will equal 'value'. Accessing this property will raise a
+      KconfigError if the expansion seems to be stuck in a loop.
+
+      Accessing this field is the same as calling expanded_value_w_args() with
+      no arguments. I hadn't considered function arguments when adding it. It
+      is retained for backwards compatibility though.
+
+    is_recursive:
+      True if the variable is recursive (defined with =).
+    """
+    __slots__ = (
+        "_n_expansions",
+        "is_recursive",
+        "kconfig",
+        "name",
+        "value",
+    )
+
+    @property
+    def expanded_value(self):
+        """
+        See the class documentation.
+        """
+        return self.expanded_value_w_args()
+
+    def expanded_value_w_args(self, *args):
+        """
+        Returns the expanded value of the variable/function. Any arguments
+        passed will be substituted for $(1), $(2), etc.
+
+        Raises a KconfigError if the expansion seems to be stuck in a loop.
+        """
+        return self.kconfig._fn_val((self.name,) + args)
+
+    def __repr__(self):
+        return "<variable {}, {}, value '{}'>" \
+               .format(self.name,
+                       "recursive" if self.is_recursive else "immediate",
+                       self.value)
+
+
+class KconfigError(Exception):
+    """
+    Exception raised for Kconfig-related errors.
+
+    KconfigError and KconfigSyntaxError are the same class. The
+    KconfigSyntaxError alias is only maintained for backwards compatibility.
+    """
+
+KconfigSyntaxError = KconfigError  # Backwards compatibility
+
+
+class InternalError(Exception):
+    "Never raised. Kept around for backwards compatibility."
+
+
+# Workaround:
+#
+# If 'errno' and 'strerror' are set on IOError, then __str__() always returns
+# "[Errno <errno>] <strerror>", ignoring any custom message passed to the
+# constructor. By defining our own subclass, we can use a custom message while
+# also providing 'errno', 'strerror', and 'filename' to scripts.
+class _KconfigIOError(IOError):
+    def __init__(self, ioerror, msg):
+        self.msg = msg
+        super(_KconfigIOError, self).__init__(
+            ioerror.errno, ioerror.strerror, ioerror.filename)
+
+    def __str__(self):
+        return self.msg
+
+
+#
+# Public functions
+#
+
+
+def expr_value(expr):
+    """
+    Evaluates the expression 'expr' to a tristate value. Returns 0 (n), 1 (m),
+    or 2 (y).
+
+    'expr' must be an already-parsed expression from a Symbol, Choice, or
+    MenuNode property. To evaluate an expression represented as a string, use
+    Kconfig.eval_string().
+
+    Passing subexpressions of expressions to this function works as expected.
+    """
+    if expr.__class__ is not tuple:
+        return expr.tri_value
+
+    if expr[0] is AND:
+        v1 = expr_value(expr[1])
+        # Short-circuit the n case as an optimization (~5% faster
+        # allnoconfig.py and allyesconfig.py, as of writing)
+        return 0 if not v1 else min(v1, expr_value(expr[2]))
+
+    if expr[0] is OR:
+        v1 = expr_value(expr[1])
+        # Short-circuit the y case as an optimization
+        return 2 if v1 == 2 else max(v1, expr_value(expr[2]))
+
+    if expr[0] is NOT:
+        return 2 - expr_value(expr[1])
+
+    # Relation
+    #
+    # Implements <, <=, >, >= comparisons as well. These were added to
+    # kconfig in 31847b67 (kconfig: allow use of relations other than
+    # (in)equality).
+
+    rel, v1, v2 = expr
+
+    # If both operands are strings...
+    if v1.orig_type is STRING and v2.orig_type is STRING:
+        # ...then compare them lexicographically
+        comp = _strcmp(v1.str_value, v2.str_value)
+    else:
+        # Otherwise, try to compare them as numbers
+        try:
+            comp = _sym_to_num(v1) - _sym_to_num(v2)
+        except ValueError:
+            # Fall back on a lexicographic comparison if the operands don't
+            # parse as numbers
+            comp = _strcmp(v1.str_value, v2.str_value)
+
+    return 2*(comp == 0 if rel is EQUAL else
+              comp != 0 if rel is UNEQUAL else
+              comp <  0 if rel is LESS else
+              comp <= 0 if rel is LESS_EQUAL else
+              comp >  0 if rel is GREATER else
+              comp >= 0)
+
+
+def standard_sc_expr_str(sc):
+    """
+    Standard symbol/choice printing function. Uses plain Kconfig syntax, and
+    displays choices as <choice> (or <choice NAME>, for named choices).
+
+    See expr_str().
+    """
+    if sc.__class__ is Symbol:
+        if sc.is_constant and sc.name not in STR_TO_TRI:
+            return '"{}"'.format(escape(sc.name))
+        return sc.name
+
+    return "<choice {}>".format(sc.name) if sc.name else "<choice>"
+
+
+def expr_str(expr, sc_expr_str_fn=standard_sc_expr_str):
+    """
+    Returns the string representation of the expression 'expr', as in a Kconfig
+    file.
+
+    Passing subexpressions of expressions to this function works as expected.
+
+    sc_expr_str_fn (default: standard_sc_expr_str):
+      This function is called for every symbol/choice (hence "sc") appearing in
+      the expression, with the symbol/choice as the argument. It is expected to
+      return a string to be used for the symbol/choice.
+
+      This can be used e.g. to turn symbols/choices into links when generating
+      documentation, or for printing the value of each symbol/choice after it.
+
+      Note that quoted values are represented as constants symbols
+      (Symbol.is_constant == True).
+    """
+    if expr.__class__ is not tuple:
+        return sc_expr_str_fn(expr)
+
+    if expr[0] is AND:
+        return "{} && {}".format(_parenthesize(expr[1], OR, sc_expr_str_fn),
+                                 _parenthesize(expr[2], OR, sc_expr_str_fn))
+
+    if expr[0] is OR:
+        # This turns A && B || C && D into "(A && B) || (C && D)", which is
+        # redundant, but more readable
+        return "{} || {}".format(_parenthesize(expr[1], AND, sc_expr_str_fn),
+                                 _parenthesize(expr[2], AND, sc_expr_str_fn))
+
+    if expr[0] is NOT:
+        if expr[1].__class__ is tuple:
+            return "!({})".format(expr_str(expr[1], sc_expr_str_fn))
+        return "!" + sc_expr_str_fn(expr[1])  # Symbol
+
+    # Relation
+    #
+    # Relation operands are always symbols (quoted strings are constant
+    # symbols)
+    return "{} {} {}".format(sc_expr_str_fn(expr[1]), REL_TO_STR[expr[0]],
+                             sc_expr_str_fn(expr[2]))
+
+
+def expr_items(expr):
+    """
+    Returns a set() of all items (symbols and choices) that appear in the
+    expression 'expr'.
+
+    Passing subexpressions of expressions to this function works as expected.
+    """
+    res = set()
+
+    def rec(subexpr):
+        if subexpr.__class__ is tuple:
+            # AND, OR, NOT, or relation
+
+            rec(subexpr[1])
+
+            # NOTs only have a single operand
+            if subexpr[0] is not NOT:
+                rec(subexpr[2])
+
+        else:
+            # Symbol or choice
+            res.add(subexpr)
+
+    rec(expr)
+    return res
+
+
+def split_expr(expr, op):
+    """
+    Returns a list containing the top-level AND or OR operands in the
+    expression 'expr', in the same (left-to-right) order as they appear in
+    the expression.
+
+    This can be handy e.g. for splitting (weak) reverse dependencies
+    from 'select' and 'imply' into individual selects/implies.
+
+    op:
+      Either AND to get AND operands, or OR to get OR operands.
+
+      (Having this as an operand might be more future-safe than having two
+      hardcoded functions.)
+
+
+    Pseudo-code examples:
+
+      split_expr( A                    , OR  )  ->  [A]
+      split_expr( A && B               , OR  )  ->  [A && B]
+      split_expr( A || B               , OR  )  ->  [A, B]
+      split_expr( A || B               , AND )  ->  [A || B]
+      split_expr( A || B || (C && D)   , OR  )  ->  [A, B, C && D]
+
+      # Second || is not at the top level
+      split_expr( A || (B && (C || D)) , OR )  ->  [A, B && (C || D)]
+
+      # Parentheses don't matter as long as we stay at the top level (don't
+      # encounter any non-'op' nodes)
+      split_expr( (A || B) || C        , OR )  ->  [A, B, C]
+      split_expr( A || (B || C)        , OR )  ->  [A, B, C]
+    """
+    res = []
+
+    def rec(subexpr):
+        if subexpr.__class__ is tuple and subexpr[0] is op:
+            rec(subexpr[1])
+            rec(subexpr[2])
+        else:
+            res.append(subexpr)
+
+    rec(expr)
+    return res
+
+
+def escape(s):
+    r"""
+    Escapes the string 's' in the same fashion as is done for display in
+    Kconfig format and when writing strings to a .config file. " and \ are
+    replaced by \" and \\, respectively.
+    """
+    # \ must be escaped before " to avoid double escaping
+    return s.replace("\\", r"\\").replace('"', r'\"')
+
+
+def unescape(s):
+    r"""
+    Unescapes the string 's'. \ followed by any character is replaced with just
+    that character. Used internally when reading .config files.
+    """
+    return _unescape_sub(r"\1", s)
+
+# unescape() helper
+_unescape_sub = re.compile(r"\\(.)").sub
+
+
+def standard_kconfig(description=None):
+    """
+    Argument parsing helper for tools that take a single optional Kconfig file
+    argument (default: Kconfig). Returns the Kconfig instance for the parsed
+    configuration. Uses argparse internally.
+
+    Exits with sys.exit() (which raises SystemExit) on errors.
+
+    description (default: None):
+      The 'description' passed to argparse.ArgumentParser().
+      argparse.RawDescriptionHelpFormatter is used, so formatting is preserved.
+    """
+    import argparse
+
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=description)
+
+    parser.add_argument(
+        "kconfig",
+        metavar="KCONFIG",
+        default="Kconfig",
+        nargs="?",
+        help="Top-level Kconfig file (default: Kconfig)")
+
+    return Kconfig(parser.parse_args().kconfig, suppress_traceback=True)
+
+
+def standard_config_filename():
+    """
+    Helper for tools. Returns the value of KCONFIG_CONFIG (which specifies the
+    .config file to load/save) if it is set, and ".config" otherwise.
+
+    Calling load_config() with filename=None might give the behavior you want,
+    without having to use this function.
+    """
+    return os.getenv("KCONFIG_CONFIG", ".config")
+
+
+def load_allconfig(kconf, filename):
+    """
+    Use Kconfig.load_allconfig() instead, which was added in Kconfiglib 13.4.0.
+    Supported for backwards compatibility. Might be removed at some point after
+    a long period of deprecation warnings.
+    """
+    allconfig = os.getenv("KCONFIG_ALLCONFIG")
+    if allconfig is None:
+        return
+
+    def std_msg(e):
+        # "Upcasts" a _KconfigIOError to an IOError, removing the custom
+        # __str__() message. The standard message is better here.
+        #
+        # This might also convert an OSError to an IOError in obscure cases,
+        # but it's probably not a big deal. The distinction is shaky (see
+        # PEP-3151).
+        return IOError(e.errno, e.strerror, e.filename)
+
+    old_warn_assign_override = kconf.warn_assign_override
+    old_warn_assign_redun = kconf.warn_assign_redun
+    kconf.warn_assign_override = kconf.warn_assign_redun = False
+
+    if allconfig in ("", "1"):
+        try:
+            print(kconf.load_config(filename, False))
+        except EnvironmentError as e1:
+            try:
+                print(kconf.load_config("all.config", False))
+            except EnvironmentError as e2:
+                sys.exit("error: KCONFIG_ALLCONFIG is set, but neither {} "
+                         "nor all.config could be opened: {}, {}"
+                         .format(filename, std_msg(e1), std_msg(e2)))
+    else:
+        try:
+            print(kconf.load_config(allconfig, False))
+        except EnvironmentError as e:
+            sys.exit("error: KCONFIG_ALLCONFIG is set to '{}', which "
+                     "could not be opened: {}"
+                     .format(allconfig, std_msg(e)))
+
+    kconf.warn_assign_override = old_warn_assign_override
+    kconf.warn_assign_redun = old_warn_assign_redun
+
+
+#
+# Internal functions
+#
+
+
+def _visibility(sc):
+    # Symbols and Choices have a "visibility" that acts as an upper bound on
+    # the values a user can set for them, corresponding to the visibility in
+    # e.g. 'make menuconfig'. This function calculates the visibility for the
+    # Symbol or Choice 'sc' -- the logic is nearly identical.
+
+    vis = 0
+
+    for node in sc.nodes:
+        if node.prompt:
+            vis = max(vis, expr_value(node.prompt[1]))
+
+    if sc.__class__ is Symbol and sc.choice:
+        if sc.choice.orig_type is TRISTATE and \
+           sc.orig_type is not TRISTATE and sc.choice.tri_value != 2:
+            # Non-tristate choice symbols are only visible in y mode
+            return 0
+
+        if sc.orig_type is TRISTATE and vis == 1 and sc.choice.tri_value == 2:
+            # Choice symbols with m visibility are not visible in y mode
+            return 0
+
+    # Promote m to y if we're dealing with a non-tristate (possibly due to
+    # modules being disabled)
+    if vis == 1 and sc.type is not TRISTATE:
+        return 2
+
+    return vis
+
+
+def _depend_on(sc, expr):
+    # Adds 'sc' (symbol or choice) as a "dependee" to all symbols in 'expr'.
+    # Constant symbols in 'expr' are skipped as they can never change value
+    # anyway.
+
+    if expr.__class__ is tuple:
+        # AND, OR, NOT, or relation
+
+        _depend_on(sc, expr[1])
+
+        # NOTs only have a single operand
+        if expr[0] is not NOT:
+            _depend_on(sc, expr[2])
+
+    elif not expr.is_constant:
+        # Non-constant symbol, or choice
+        expr._dependents.add(sc)
+
+
+def _parenthesize(expr, type_, sc_expr_str_fn):
+    # expr_str() helper. Adds parentheses around expressions of type 'type_'.
+
+    if expr.__class__ is tuple and expr[0] is type_:
+        return "({})".format(expr_str(expr, sc_expr_str_fn))
+    return expr_str(expr, sc_expr_str_fn)
+
+
+def _ordered_unique(lst):
+    # Returns 'lst' with any duplicates removed, preserving order. This hacky
+    # version seems to be a common idiom. It relies on short-circuit evaluation
+    # and set.add() returning None, which is falsy.
+
+    seen = set()
+    seen_add = seen.add
+    return [x for x in lst if x not in seen and not seen_add(x)]
+
+
+def _is_base_n(s, n):
+    try:
+        int(s, n)
+        return True
+    except ValueError:
+        return False
+
+
+def _strcmp(s1, s2):
+    # strcmp()-alike that returns -1, 0, or 1
+
+    return (s1 > s2) - (s1 < s2)
+
+
+def _sym_to_num(sym):
+    # expr_value() helper for converting a symbol to a number. Raises
+    # ValueError for symbols that can't be converted.
+
+    # For BOOL and TRISTATE, n/m/y count as 0/1/2. This mirrors 9059a3493ef
+    # ("kconfig: fix relational operators for bool and tristate symbols") in
+    # the C implementation.
+    return sym.tri_value if sym.orig_type in _BOOL_TRISTATE else \
+           int(sym.str_value, _TYPE_TO_BASE[sym.orig_type])
+
+
+def _touch_dep_file(path, sym_name):
+    # If sym_name is MY_SYM_NAME, touches my/sym/name.h. See the sync_deps()
+    # docstring.
+
+    sym_path = path + os.sep + sym_name.lower().replace("_", os.sep) + ".h"
+    sym_path_dir = dirname(sym_path)
+    if not exists(sym_path_dir):
+        os.makedirs(sym_path_dir, 0o755)
+
+    # A kind of truncating touch, mirroring the C tools
+    os.close(os.open(
+        sym_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644))
+
+
+def _save_old(path):
+    # See write_config()
+
+    def copy(src, dst):
+        # Import as needed, to save some startup time
+        import shutil
+        shutil.copyfile(src, dst)
+
+    if islink(path):
+        # Preserve symlinks
+        copy_fn = copy
+    elif hasattr(os, "replace"):
+        # Python 3 (3.3+) only. Best choice when available, because it
+        # removes <filename>.old on both *nix and Windows.
+        copy_fn = os.replace
+    elif os.name == "posix":
+        # Removes <filename>.old on POSIX systems
+        copy_fn = os.rename
+    else:
+        # Fall back on copying
+        copy_fn = copy
+
+    try:
+        copy_fn(path, path + ".old")
+    except Exception:
+        # Ignore errors from 'path' missing as well as other errors.
+        # <filename>.old file is usually more of a nice-to-have, and not worth
+        # erroring out over e.g. if <filename>.old happens to be a directory or
+        # <filename> is something like /dev/null.
+        pass
+
+
+def _locs(sc):
+    # Symbol/Choice.name_and_loc helper. Returns the "(defined at ...)" part of
+    # the string. 'sc' is a Symbol or Choice.
+
+    if sc.nodes:
+        return "(defined at {})".format(
+            ", ".join("{0.filename}:{0.linenr}".format(node)
+                      for node in sc.nodes))
+
+    return "(undefined)"
+
+
+# Menu manipulation
+
+
+def _expr_depends_on(expr, sym):
+    # Reimplementation of expr_depends_symbol() from mconf.c. Used to determine
+    # if a submenu should be implicitly created. This also influences which
+    # items inside choice statements are considered choice items.
+
+    if expr.__class__ is not tuple:
+        return expr is sym
+
+    if expr[0] in _EQUAL_UNEQUAL:
+        # Check for one of the following:
+        # sym = m/y, m/y = sym, sym != n, n != sym
+
+        left, right = expr[1:]
+
+        if right is sym:
+            left, right = right, left
+        elif left is not sym:
+            return False
+
+        return (expr[0] is EQUAL and right is sym.kconfig.m or
+                                     right is sym.kconfig.y) or \
+               (expr[0] is UNEQUAL and right is sym.kconfig.n)
+
+    return expr[0] is AND and \
+           (_expr_depends_on(expr[1], sym) or
+            _expr_depends_on(expr[2], sym))
+
+
+def _auto_menu_dep(node1, node2):
+    # Returns True if node2 has an "automatic menu dependency" on node1. If
+    # node2 has a prompt, we check its condition. Otherwise, we look directly
+    # at node2.dep.
+
+    return _expr_depends_on(node2.prompt[1] if node2.prompt else node2.dep,
+                            node1.item)
+
+
+def _flatten(node):
+    # "Flattens" menu nodes without prompts (e.g. 'if' nodes and non-visible
+    # symbols with children from automatic menu creation) so that their
+    # children appear after them instead. This gives a clean menu structure
+    # with no unexpected "jumps" in the indentation.
+    #
+    # Do not flatten promptless choices (which can appear "legitimately" if a
+    # named choice is defined in multiple locations to add on symbols). It
+    # looks confusing, and the menuconfig already shows all choice symbols if
+    # you enter the choice at some location with a prompt.
+
+    while node:
+        if node.list and not node.prompt and \
+           node.item.__class__ is not Choice:
+
+            last_node = node.list
+            while 1:
+                last_node.parent = node.parent
+                if not last_node.next:
+                    break
+                last_node = last_node.next
+
+            last_node.next = node.next
+            node.next = node.list
+            node.list = None
+
+        node = node.next
+
+
+def _remove_ifs(node):
+    # Removes 'if' nodes (which can be recognized by MenuNode.item being None),
+    # which are assumed to already have been flattened. The C implementation
+    # doesn't bother to do this, but we expose the menu tree directly, and it
+    # makes it nicer to work with.
+
+    cur = node.list
+    while cur and not cur.item:
+        cur = cur.next
+
+    node.list = cur
+
+    while cur:
+        next = cur.next
+        while next and not next.item:
+            next = next.next
+
+        # Equivalent to
+        #
+        #   cur.next = next
+        #   cur = next
+        #
+        # due to tricky Python semantics. The order matters.
+        cur.next = cur = next
+
+
+def _finalize_choice(node):
+    # Finalizes a choice, marking each symbol whose menu node has the choice as
+    # the parent as a choice symbol, and automatically determining types if not
+    # specified.
+
+    choice = node.item
+
+    cur = node.list
+    while cur:
+        if cur.item.__class__ is Symbol:
+            cur.item.choice = choice
+            choice.syms.append(cur.item)
+        cur = cur.next
+
+    # If no type is specified for the choice, its type is that of
+    # the first choice item with a specified type
+    if not choice.orig_type:
+        for item in choice.syms:
+            if item.orig_type:
+                choice.orig_type = item.orig_type
+                break
+
+    # Each choice item of UNKNOWN type gets the type of the choice
+    for sym in choice.syms:
+        if not sym.orig_type:
+            sym.orig_type = choice.orig_type
+
+
+def _check_dep_loop_sym(sym, ignore_choice):
+    # Detects dependency loops using depth-first search on the dependency graph
+    # (which is calculated earlier in Kconfig._build_dep()).
+    #
+    # Algorithm:
+    #
+    #  1. Symbols/choices start out with _visited = 0, meaning unvisited.
+    #
+    #  2. When a symbol/choice is first visited, _visited is set to 1, meaning
+    #     "visited, potentially part of a dependency loop". The recursive
+    #     search then continues from the symbol/choice.
+    #
+    #  3. If we run into a symbol/choice X with _visited already set to 1,
+    #     there's a dependency loop. The loop is found on the call stack by
+    #     recording symbols while returning ("on the way back") until X is seen
+    #     again.
+    #
+    #  4. Once a symbol/choice and all its dependencies (or dependents in this
+    #     case) have been checked recursively without detecting any loops, its
+    #     _visited is set to 2, meaning "visited, not part of a dependency
+    #     loop".
+    #
+    #     This saves work if we run into the symbol/choice again in later calls
+    #     to _check_dep_loop_sym(). We just return immediately.
+    #
+    # Choices complicate things, as every choice symbol depends on every other
+    # choice symbol in a sense. When a choice is "entered" via a choice symbol
+    # X, we visit all choice symbols from the choice except X, and prevent
+    # immediately revisiting the choice with a flag (ignore_choice).
+    #
+    # Maybe there's a better way to handle this (different flags or the
+    # like...)
+
+    if not sym._visited:
+        # sym._visited == 0, unvisited
+
+        sym._visited = 1
+
+        for dep in sym._dependents:
+            # Choices show up in Symbol._dependents when the choice has the
+            # symbol in a 'prompt' or 'default' condition (e.g.
+            # 'default ... if SYM').
+            #
+            # Since we aren't entering the choice via a choice symbol, all
+            # choice symbols need to be checked, hence the None.
+            loop = _check_dep_loop_choice(dep, None) \
+                   if dep.__class__ is Choice \
+                   else _check_dep_loop_sym(dep, False)
+
+            if loop:
+                # Dependency loop found
+                return _found_dep_loop(loop, sym)
+
+        if sym.choice and not ignore_choice:
+            loop = _check_dep_loop_choice(sym.choice, sym)
+            if loop:
+                # Dependency loop found
+                return _found_dep_loop(loop, sym)
+
+        # The symbol is not part of a dependency loop
+        sym._visited = 2
+
+        # No dependency loop found
+        return None
+
+    if sym._visited == 2:
+        # The symbol was checked earlier and is already known to not be part of
+        # a dependency loop
+        return None
+
+    # sym._visited == 1, found a dependency loop. Return the symbol as the
+    # first element in it.
+    return (sym,)
+
+
+def _check_dep_loop_choice(choice, skip):
+    if not choice._visited:
+        # choice._visited == 0, unvisited
+
+        choice._visited = 1
+
+        # Check for loops involving choice symbols. If we came here via a
+        # choice symbol, skip that one, as we'd get a false positive
+        # '<sym FOO> -> <choice> -> <sym FOO>' loop otherwise.
+        for sym in choice.syms:
+            if sym is not skip:
+                # Prevent the choice from being immediately re-entered via the
+                # "is a choice symbol" path by passing True
+                loop = _check_dep_loop_sym(sym, True)
+                if loop:
+                    # Dependency loop found
+                    return _found_dep_loop(loop, choice)
+
+        # The choice is not part of a dependency loop
+        choice._visited = 2
+
+        # No dependency loop found
+        return None
+
+    if choice._visited == 2:
+        # The choice was checked earlier and is already known to not be part of
+        # a dependency loop
+        return None
+
+    # choice._visited == 1, found a dependency loop. Return the choice as the
+    # first element in it.
+    return (choice,)
+
+
+def _found_dep_loop(loop, cur):
+    # Called "on the way back" when we know we have a loop
+
+    # Is the symbol/choice 'cur' where the loop started?
+    if cur is not loop[0]:
+        # Nope, it's just a part of the loop
+        return loop + (cur,)
+
+    # Yep, we have the entire loop. Throw an exception that shows it.
+
+    msg = "\nDependency loop\n" \
+            "===============\n\n"
+
+    for item in loop:
+        if item is not loop[0]:
+            msg += "...depends on "
+            if item.__class__ is Symbol and item.choice:
+                msg += "the choice symbol "
+
+        msg += "{}, with definition...\n\n{}\n\n" \
+               .format(item.name_and_loc, item)
+
+        # Small wart: Since we reuse the already calculated
+        # Symbol/Choice._dependents sets for recursive dependency detection, we
+        # lose information on whether a dependency came from a 'select'/'imply'
+        # condition or e.g. a 'depends on'.
+        #
+        # This might cause selecting symbols to "disappear". For example,
+        # a symbol B having 'select A if C' gives a direct dependency from A to
+        # C, since it corresponds to a reverse dependency of B && C.
+        #
+        # Always print reverse dependencies for symbols that have them to make
+        # sure information isn't lost. I wonder if there's some neat way to
+        # improve this.
+
+        if item.__class__ is Symbol:
+            if item.rev_dep is not item.kconfig.n:
+                msg += "(select-related dependencies: {})\n\n" \
+                       .format(expr_str(item.rev_dep))
+
+            if item.weak_rev_dep is not item.kconfig.n:
+                msg += "(imply-related dependencies: {})\n\n" \
+                       .format(expr_str(item.rev_dep))
+
+    msg += "...depends again on " + loop[0].name_and_loc
+
+    raise KconfigError(msg)
+
+
+def _decoding_error(e, filename, macro_linenr=None):
+    # Gives the filename and context for UnicodeDecodeError's, which are a pain
+    # to debug otherwise. 'e' is the UnicodeDecodeError object.
+    #
+    # If the decoding error is for the output of a $(shell,...) command,
+    # macro_linenr holds the line number where it was run (the exact line
+    # number isn't available for decoding errors in files).
+
+    raise KconfigError(
+        "\n"
+        "Malformed {} in {}\n"
+        "Context: {}\n"
+        "Problematic data: {}\n"
+        "Reason: {}".format(
+            e.encoding,
+            "'{}'".format(filename) if macro_linenr is None else
+                "output from macro at {}:{}".format(filename, macro_linenr),
+            e.object[max(e.start - 40, 0):e.end + 40],
+            e.object[e.start:e.end],
+            e.reason))
+
+
+def _warn_verbose_deprecated(fn_name):
+    sys.stderr.write(
+        "Deprecation warning: {0}()'s 'verbose' argument has no effect. Since "
+        "Kconfiglib 12.0.0, the message is returned from {0}() instead, "
+        "and is always generated. Do e.g. print(kconf.{0}()) if you want to "
+        "want to show a message like \"Loaded configuration '.config'\" on "
+        "stdout. The old API required ugly hacks to reuse messages in "
+        "configuration interfaces.\n".format(fn_name))
+
+
+# Predefined preprocessor functions
+
+
+def _filename_fn(kconf, _):
+    return kconf.filename
+
+
+def _lineno_fn(kconf, _):
+    return str(kconf.linenr)
+
+
+def _info_fn(kconf, _, msg):
+    print("{}:{}: {}".format(kconf.filename, kconf.linenr, msg))
+
+    return ""
+
+
+def _warning_if_fn(kconf, _, cond, msg):
+    if cond == "y":
+        kconf._warn(msg, kconf.filename, kconf.linenr)
+
+    return ""
+
+
+def _error_if_fn(kconf, _, cond, msg):
+    if cond == "y":
+        raise KconfigError("{}:{}: {}".format(
+            kconf.filename, kconf.linenr, msg))
+
+    return ""
+
+
+def _shell_fn(kconf, _, command):
+    import subprocess  # Only import as needed, to save some startup time
+
+    stdout, stderr = subprocess.Popen(
+        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+    ).communicate()
+
+    if not _IS_PY2:
+        try:
+            stdout = stdout.decode(kconf._encoding)
+            stderr = stderr.decode(kconf._encoding)
+        except UnicodeDecodeError as e:
+            _decoding_error(e, kconf.filename, kconf.linenr)
+
+    if stderr:
+        kconf._warn("'{}' wrote to stderr: {}".format(
+                        command, "\n".join(stderr.splitlines())),
+                    kconf.filename, kconf.linenr)
+
+    # Universal newlines with splitlines() (to prevent e.g. stray \r's in
+    # command output on Windows), trailing newline removal, and
+    # newline-to-space conversion.
+    #
+    # On Python 3 versions before 3.6, it's not possible to specify the
+    # encoding when passing universal_newlines=True to Popen() (the 'encoding'
+    # parameter was added in 3.6), so we do this manual version instead.
+    return "\n".join(stdout.splitlines()).rstrip("\n").replace("\n", " ")
+
+#
+# Global constants
+#
+
+TRI_TO_STR = {
+    0: "n",
+    1: "m",
+    2: "y",
+}
+
+STR_TO_TRI = {
+    "n": 0,
+    "m": 1,
+    "y": 2,
+}
+
+# Constant representing that there's no cached choice selection. This is
+# distinct from a cached None (no selection). Any object that's not None or a
+# Symbol will do. We test this with 'is'.
+_NO_CACHED_SELECTION = 0
+
+# Are we running on Python 2?
+_IS_PY2 = sys.version_info[0] < 3
+
+try:
+    _UNAME_RELEASE = os.uname()[2]
+except AttributeError:
+    # Only import as needed, to save some startup time
+    import platform
+    _UNAME_RELEASE = platform.uname()[2]
+
+# The token and type constants below are safe to test with 'is', which is a bit
+# faster (~30% faster on my machine, and a few % faster for total parsing
+# time), even without assuming Python's small integer optimization (which
+# caches small integer objects). The constants end up pointing to unique
+# integer objects, and since we consistently refer to them via the names below,
+# we always get the same object.
+#
+# Client code should use == though.
+
+# Tokens, with values 1, 2, ... . Avoiding 0 simplifies some checks by making
+# all tokens except empty strings truthy.
+(
+    _T_ALLNOCONFIG_Y,
+    _T_AND,
+    _T_BOOL,
+    _T_CHOICE,
+    _T_CLOSE_PAREN,
+    _T_COMMENT,
+    _T_CONFIG,
+    _T_DEFAULT,
+    _T_DEFCONFIG_LIST,
+    _T_DEF_BOOL,
+    _T_DEF_HEX,
+    _T_DEF_INT,
+    _T_DEF_STRING,
+    _T_DEF_TRISTATE,
+    _T_DEPENDS,
+    _T_ENDCHOICE,
+    _T_ENDIF,
+    _T_ENDMENU,
+    _T_ENV,
+    _T_EQUAL,
+    _T_GREATER,
+    _T_GREATER_EQUAL,
+    _T_HELP,
+    _T_HEX,
+    _T_IF,
+    _T_IMPLY,
+    _T_INT,
+    _T_LESS,
+    _T_LESS_EQUAL,
+    _T_MAINMENU,
+    _T_MENU,
+    _T_MENUCONFIG,
+    _T_MODULES,
+    _T_NOT,
+    _T_ON,
+    _T_OPEN_PAREN,
+    _T_OPTION,
+    _T_OPTIONAL,
+    _T_OR,
+    _T_ORSOURCE,
+    _T_OSOURCE,
+    _T_PROMPT,
+    _T_RANGE,
+    _T_RSOURCE,
+    _T_SELECT,
+    _T_SOURCE,
+    _T_STRING,
+    _T_TRISTATE,
+    _T_UNEQUAL,
+    _T_VISIBLE,
+) = range(1, 51)
+
+# Keyword to token map, with the get() method assigned directly as a small
+# optimization
+_get_keyword = {
+    "---help---":     _T_HELP,
+    "allnoconfig_y":  _T_ALLNOCONFIG_Y,
+    "bool":           _T_BOOL,
+    "boolean":        _T_BOOL,
+    "choice":         _T_CHOICE,
+    "comment":        _T_COMMENT,
+    "config":         _T_CONFIG,
+    "def_bool":       _T_DEF_BOOL,
+    "def_hex":        _T_DEF_HEX,
+    "def_int":        _T_DEF_INT,
+    "def_string":     _T_DEF_STRING,
+    "def_tristate":   _T_DEF_TRISTATE,
+    "default":        _T_DEFAULT,
+    "defconfig_list": _T_DEFCONFIG_LIST,
+    "depends":        _T_DEPENDS,
+    "endchoice":      _T_ENDCHOICE,
+    "endif":          _T_ENDIF,
+    "endmenu":        _T_ENDMENU,
+    "env":            _T_ENV,
+    "grsource":       _T_ORSOURCE,  # Backwards compatibility
+    "gsource":        _T_OSOURCE,   # Backwards compatibility
+    "help":           _T_HELP,
+    "hex":            _T_HEX,
+    "if":             _T_IF,
+    "imply":          _T_IMPLY,
+    "int":            _T_INT,
+    "mainmenu":       _T_MAINMENU,
+    "menu":           _T_MENU,
+    "menuconfig":     _T_MENUCONFIG,
+    "modules":        _T_MODULES,
+    "on":             _T_ON,
+    "option":         _T_OPTION,
+    "optional":       _T_OPTIONAL,
+    "orsource":       _T_ORSOURCE,
+    "osource":        _T_OSOURCE,
+    "prompt":         _T_PROMPT,
+    "range":          _T_RANGE,
+    "rsource":        _T_RSOURCE,
+    "select":         _T_SELECT,
+    "source":         _T_SOURCE,
+    "string":         _T_STRING,
+    "tristate":       _T_TRISTATE,
+    "visible":        _T_VISIBLE,
+}.get
+
+# The constants below match the value of the corresponding tokens to remove the
+# need for conversion
+
+# Node types
+MENU    = _T_MENU
+COMMENT = _T_COMMENT
+
+# Expression types
+AND           = _T_AND
+OR            = _T_OR
+NOT           = _T_NOT
+EQUAL         = _T_EQUAL
+UNEQUAL       = _T_UNEQUAL
+LESS          = _T_LESS
+LESS_EQUAL    = _T_LESS_EQUAL
+GREATER       = _T_GREATER
+GREATER_EQUAL = _T_GREATER_EQUAL
+
+REL_TO_STR = {
+    EQUAL:         "=",
+    UNEQUAL:       "!=",
+    LESS:          "<",
+    LESS_EQUAL:    "<=",
+    GREATER:       ">",
+    GREATER_EQUAL: ">=",
+}
+
+# Symbol/choice types. UNKNOWN is 0 (falsy) to simplify some checks.
+# Client code shouldn't rely on it though, as it was non-zero in
+# older versions.
+UNKNOWN  = 0
+BOOL     = _T_BOOL
+TRISTATE = _T_TRISTATE
+STRING   = _T_STRING
+INT      = _T_INT
+HEX      = _T_HEX
+
+TYPE_TO_STR = {
+    UNKNOWN:  "unknown",
+    BOOL:     "bool",
+    TRISTATE: "tristate",
+    STRING:   "string",
+    INT:      "int",
+    HEX:      "hex",
+}
+
+# Used in comparisons. 0 means the base is inferred from the format of the
+# string.
+_TYPE_TO_BASE = {
+    HEX:      16,
+    INT:      10,
+    STRING:   0,
+    UNKNOWN:  0,
+}
+
+# def_bool -> BOOL, etc.
+_DEF_TOKEN_TO_TYPE = {
+    _T_DEF_BOOL:     BOOL,
+    _T_DEF_HEX:      HEX,
+    _T_DEF_INT:      INT,
+    _T_DEF_STRING:   STRING,
+    _T_DEF_TRISTATE: TRISTATE,
+}
+
+# Tokens after which strings are expected. This is used to tell strings from
+# constant symbol references during tokenization, both of which are enclosed in
+# quotes.
+#
+# Identifier-like lexemes ("missing quotes") are also treated as strings after
+# these tokens. _T_CHOICE is included to avoid symbols being registered for
+# named choices.
+_STRING_LEX = frozenset({
+    _T_BOOL,
+    _T_CHOICE,
+    _T_COMMENT,
+    _T_HEX,
+    _T_INT,
+    _T_MAINMENU,
+    _T_MENU,
+    _T_ORSOURCE,
+    _T_OSOURCE,
+    _T_PROMPT,
+    _T_RSOURCE,
+    _T_SOURCE,
+    _T_STRING,
+    _T_TRISTATE,
+})
+
+# Various sets for quick membership tests. Gives a single global lookup and
+# avoids creating temporary dicts/tuples.
+
+_TYPE_TOKENS = frozenset({
+    _T_BOOL,
+    _T_TRISTATE,
+    _T_INT,
+    _T_HEX,
+    _T_STRING,
+})
+
+_SOURCE_TOKENS = frozenset({
+    _T_SOURCE,
+    _T_RSOURCE,
+    _T_OSOURCE,
+    _T_ORSOURCE,
+})
+
+_REL_SOURCE_TOKENS = frozenset({
+    _T_RSOURCE,
+    _T_ORSOURCE,
+})
+
+# Obligatory (non-optional) sources
+_OBL_SOURCE_TOKENS = frozenset({
+    _T_SOURCE,
+    _T_RSOURCE,
+})
+
+_BOOL_TRISTATE = frozenset({
+    BOOL,
+    TRISTATE,
+})
+
+_BOOL_TRISTATE_UNKNOWN = frozenset({
+    BOOL,
+    TRISTATE,
+    UNKNOWN,
+})
+
+_INT_HEX = frozenset({
+    INT,
+    HEX,
+})
+
+_SYMBOL_CHOICE = frozenset({
+    Symbol,
+    Choice,
+})
+
+_MENU_COMMENT = frozenset({
+    MENU,
+    COMMENT,
+})
+
+_EQUAL_UNEQUAL = frozenset({
+    EQUAL,
+    UNEQUAL,
+})
+
+_RELATIONS = frozenset({
+    EQUAL,
+    UNEQUAL,
+    LESS,
+    LESS_EQUAL,
+    GREATER,
+    GREATER_EQUAL,
+})
+
+# Helper functions for getting compiled regular expressions, with the needed
+# matching function returned directly as a small optimization.
+#
+# Use ASCII regex matching on Python 3. It's already the default on Python 2.
+
+
+def _re_match(regex):
+    return re.compile(regex, 0 if _IS_PY2 else re.ASCII).match
+
+
+def _re_search(regex):
+    return re.compile(regex, 0 if _IS_PY2 else re.ASCII).search
+
+
+# Various regular expressions used during parsing
+
+# The initial token on a line. Also eats leading and trailing whitespace, so
+# that we can jump straight to the next token (or to the end of the line if
+# there is only one token).
+#
+# This regex will also fail to match for empty lines and comment lines.
+#
+# '$' is included to detect preprocessor variable assignments with macro
+# expansions in the left-hand side.
+_command_match = _re_match(r"\s*([A-Za-z0-9_$-]+)\s*")
+
+# An identifier/keyword after the first token. Also eats trailing whitespace.
+# '$' is included to detect identifiers containing macro expansions.
+_id_keyword_match = _re_match(r"([A-Za-z0-9_$/.-]+)\s*")
+
+# A fragment in the left-hand side of a preprocessor variable assignment. These
+# are the portions between macro expansions ($(foo)). Macros are supported in
+# the LHS (variable name).
+_assignment_lhs_fragment_match = _re_match("[A-Za-z0-9_-]*")
+
+# The assignment operator and value (right-hand side) in a preprocessor
+# variable assignment
+_assignment_rhs_match = _re_match(r"\s*(=|:=|\+=)\s*(.*)")
+
+# Special characters/strings while expanding a macro ('(', ')', ',', and '$(')
+_macro_special_search = _re_search(r"\(|\)|,|\$\(")
+
+# Special characters/strings while expanding a string (quotes, '\', and '$(')
+_string_special_search = _re_search(r'"|\'|\\|\$\(')
+
+# Special characters/strings while expanding a symbol name. Also includes
+# end-of-line, in case the macro is the last thing on the line.
+_name_special_search = _re_search(r'[^A-Za-z0-9_$/.-]|\$\(|$')
+
+# A valid right-hand side for an assignment to a string symbol in a .config
+# file, including escaped characters. Extracts the contents.
+_conf_string_match = _re_match(r'"((?:[^\\"]|\\.)*)"')
diff --git a/scripts/Kconfiglib/menuconfig.py b/scripts/Kconfiglib/menuconfig.py
new file mode 100755
index 0000000..7e765d3
--- /dev/null
+++ b/scripts/Kconfiglib/menuconfig.py
@@ -0,0 +1,3278 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Overview
+========
+
+A curses-based Python 2/3 menuconfig implementation. The interface should feel
+familiar to people used to mconf ('make menuconfig').
+
+Supports the same keys as mconf, and also supports a set of keybindings
+inspired by Vi:
+
+  J/K     : Down/Up
+  L       : Enter menu/Toggle item
+  H       : Leave menu
+  Ctrl-D/U: Page Down/Page Up
+  G/End   : Jump to end of list
+  g/Home  : Jump to beginning of list
+
+[Space] toggles values if possible, and enters menus otherwise. [Enter] works
+the other way around.
+
+The mconf feature where pressing a key jumps to a menu entry with that
+character in it in the current menu isn't supported. A jump-to feature for
+jumping directly to any symbol (including invisible symbols), choice, menu or
+comment (as in a Kconfig 'comment "Foo"') is available instead.
+
+A few different modes are available:
+
+  F: Toggle show-help mode, which shows the help text of the currently selected
+  item in the window at the bottom of the menu display. This is handy when
+  browsing through options.
+
+  C: Toggle show-name mode, which shows the symbol name before each symbol menu
+  entry
+
+  A: Toggle show-all mode, which shows all items, including currently invisible
+  items and items that lack a prompt. Invisible items are drawn in a different
+  style to make them stand out.
+
+
+Running
+=======
+
+menuconfig.py can be run either as a standalone executable or by calling the
+menuconfig() function with an existing Kconfig instance. The second option is a
+bit inflexible in that it will still load and save .config, etc.
+
+When run in standalone mode, the top-level Kconfig file to load can be passed
+as a command-line argument. With no argument, it defaults to "Kconfig".
+
+The KCONFIG_CONFIG environment variable specifies the .config file to load (if
+it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used.
+
+When overwriting a configuration file, the old version is saved to
+<filename>.old (e.g. .config.old).
+
+$srctree is supported through Kconfiglib.
+
+
+Color schemes
+=============
+
+It is possible to customize the color scheme by setting the MENUCONFIG_STYLE
+environment variable. For example, setting it to 'aquatic' will enable an
+alternative, less yellow, more 'make menuconfig'-like color scheme, contributed
+by Mitja Horvat (pinkfluid).
+
+This is the current list of built-in styles:
+    - default       classic Kconfiglib theme with a yellow accent
+    - monochrome    colorless theme (uses only bold and standout) attributes,
+                    this style is used if the terminal doesn't support colors
+    - aquatic       blue-tinted style loosely resembling the lxdialog theme
+
+It is possible to customize the current style by changing colors of UI
+elements on the screen. This is the list of elements that can be stylized:
+
+    - path          Top row in the main display, with the menu path
+    - separator     Separator lines between windows. Also used for the top line
+                    in the symbol information display.
+    - list          List of items, e.g. the main display
+    - selection     Style for the selected item
+    - inv-list      Like list, but for invisible items. Used in show-all mode.
+    - inv-selection Like selection, but for invisible items. Used in show-all
+                    mode.
+    - help          Help text windows at the bottom of various fullscreen
+                    dialogs
+    - show-help     Window showing the help text in show-help mode
+    - frame         Frame around dialog boxes
+    - body          Body of dialog boxes
+    - edit          Edit box in pop-up dialogs
+    - jump-edit     Edit box in jump-to dialog
+    - text          Symbol information text
+
+The color definition is a comma separated list of attributes:
+
+    - fg:COLOR      Set the foreground/background colors. COLOR can be one of
+      * or *        the basic 16 colors (black, red, green, yellow, blue,
+    - bg:COLOR      magenta, cyan, white and brighter versions, for example,
+                    brightred). On terminals that support more than 8 colors,
+                    you can also directly put in a color number, e.g. fg:123
+                    (hexadecimal and octal constants are accepted as well).
+                    Colors outside the range -1..curses.COLORS-1 (which is
+                    terminal-dependent) are ignored (with a warning). The COLOR
+                    can be also specified using a RGB value in the HTML
+                    notation, for example #RRGGBB. If the terminal supports
+                    color changing, the color is rendered accurately.
+                    Otherwise, the visually nearest color is used.
+
+                    If the background or foreground color of an element is not
+                    specified, it defaults to -1, representing the default
+                    terminal foreground or background color.
+
+                    Note: On some terminals a bright version of the color
+                    implies bold.
+    - bold          Use bold text
+    - underline     Use underline text
+    - standout      Standout text attribute (reverse color)
+
+More often than not, some UI elements share the same color definition. In such
+cases the right value may specify an UI element from which the color definition
+will be copied. For example, "separator=help" will apply the current color
+definition for "help" to "separator".
+
+A keyword without the '=' is assumed to be a style template. The template name
+is looked up in the built-in styles list and the style definition is expanded
+in-place. With this, built-in styles can be used as basis for new styles.
+
+For example, take the aquatic theme and give it a red selection bar:
+
+MENUCONFIG_STYLE="aquatic selection=fg:white,bg:red"
+
+If there's an error in the style definition or if a missing style is assigned
+to, the assignment will be ignored, along with a warning being printed on
+stderr.
+
+The 'default' theme is always implicitly parsed first, so the following two
+settings have the same effect:
+
+    MENUCONFIG_STYLE="selection=fg:white,bg:red"
+    MENUCONFIG_STYLE="default selection=fg:white,bg:red"
+
+If the terminal doesn't support colors, the 'monochrome' theme is used, and
+MENUCONFIG_STYLE is ignored. The assumption is that the environment is broken
+somehow, and that the important thing is to get something usable.
+
+
+Other features
+==============
+
+  - Seamless terminal resizing
+
+  - No dependencies on *nix, as the 'curses' module is in the Python standard
+    library
+
+  - Unicode text entry
+
+  - Improved information screen compared to mconf:
+
+      * Expressions are split up by their top-level &&/|| operands to improve
+        readability
+
+      * Undefined symbols in expressions are pointed out
+
+      * Menus and comments have information displays
+
+      * Kconfig definitions are printed
+
+      * The include path is shown, listing the locations of the 'source'
+        statements that included the Kconfig file of the symbol (or other
+        item)
+
+
+Limitations
+===========
+
+Doesn't work out of the box on Windows, but can be made to work with
+
+    pip install windows-curses
+
+See the https://github.com/zephyrproject-rtos/windows-curses repository.
+"""
+from __future__ import print_function
+
+import os
+import sys
+
+_IS_WINDOWS = os.name == "nt"  # Are we running on Windows?
+
+try:
+    import curses
+except ImportError as e:
+    if not _IS_WINDOWS:
+        raise
+    sys.exit("""\
+menuconfig failed to import the standard Python 'curses' library. Try
+installing a package like windows-curses
+(https://github.com/zephyrproject-rtos/windows-curses) by running this command
+in cmd.exe:
+
+    pip install windows-curses
+
+Starting with Kconfiglib 13.0.0, windows-curses is no longer automatically
+installed when installing Kconfiglib via pip on Windows (because it breaks
+installation on MSYS2).
+
+Exception:
+{}: {}""".format(type(e).__name__, e))
+
+import errno
+import locale
+import re
+import textwrap
+
+from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \
+                       BOOL, TRISTATE, STRING, INT, HEX, \
+                       AND, OR, \
+                       expr_str, expr_value, split_expr, \
+                       standard_sc_expr_str, \
+                       TRI_TO_STR, TYPE_TO_STR, \
+                       standard_kconfig, standard_config_filename
+
+
+#
+# Configuration variables
+#
+
+# If True, try to change LC_CTYPE to a UTF-8 locale if it is set to the C
+# locale (which implies ASCII). This fixes curses Unicode I/O issues on systems
+# with bad defaults. ncurses configures itself from the locale settings.
+#
+# Related PEP: https://www.python.org/dev/peps/pep-0538/
+_CHANGE_C_LC_CTYPE_TO_UTF8 = True
+
+# How many steps an implicit submenu will be indented. Implicit submenus are
+# created when an item depends on the symbol before it. Note that symbols
+# defined with 'menuconfig' create a separate menu instead of indenting.
+_SUBMENU_INDENT = 4
+
+# Number of steps for Page Up/Down to jump
+_PG_JUMP = 6
+
+# Height of the help window in show-help mode
+_SHOW_HELP_HEIGHT = 8
+
+# How far the cursor needs to be from the edge of the window before it starts
+# to scroll. Used for the main menu display, the information display, the
+# search display, and for text boxes.
+_SCROLL_OFFSET = 5
+
+# Minimum width of dialogs that ask for text input
+_INPUT_DIALOG_MIN_WIDTH = 30
+
+# Number of arrows pointing up/down to draw when a window is scrolled
+_N_SCROLL_ARROWS = 14
+
+# Lines of help text shown at the bottom of the "main" display
+_MAIN_HELP_LINES = """
+[Space/Enter] Toggle/enter  [ESC] Leave menu           [S] Save
+[O] Load                    [?] Symbol info            [/] Jump to symbol
+[F] Toggle show-help mode   [C] Toggle show-name mode  [A] Toggle show-all mode
+[Q] Quit (prompts for save) [D] Save minimal config (advanced)
+"""[1:-1].split("\n")
+
+# Lines of help text shown at the bottom of the information dialog
+_INFO_HELP_LINES = """
+[ESC/q] Return to menu      [/] Jump to symbol
+"""[1:-1].split("\n")
+
+# Lines of help text shown at the bottom of the search dialog
+_JUMP_TO_HELP_LINES = """
+Type text to narrow the search. Regexes are supported (via Python's 're'
+module). The up/down cursor keys step in the list. [Enter] jumps to the
+selected symbol. [ESC] aborts the search. Type multiple space-separated
+strings/regexes to find entries that match all of them. Type Ctrl-F to
+view the help of the selected item without leaving the dialog.
+"""[1:-1].split("\n")
+
+#
+# Styling
+#
+
+_STYLES = {
+    "default": """
+    path=fg:black,bg:white,bold
+    separator=fg:black,bg:yellow,bold
+    list=fg:black,bg:white
+    selection=fg:white,bg:blue,bold
+    inv-list=fg:red,bg:white
+    inv-selection=fg:red,bg:blue
+    help=path
+    show-help=list
+    frame=fg:black,bg:yellow,bold
+    body=fg:white,bg:black
+    edit=fg:white,bg:blue
+    jump-edit=edit
+    text=list
+    """,
+
+    # This style is forced on terminals that do no support colors
+    "monochrome": """
+    path=bold
+    separator=bold,standout
+    list=
+    selection=bold,standout
+    inv-list=bold
+    inv-selection=bold,standout
+    help=bold
+    show-help=
+    frame=bold,standout
+    body=
+    edit=standout
+    jump-edit=
+    text=
+    """,
+
+    # Blue-tinted style loosely resembling lxdialog
+    "aquatic": """
+    path=fg:white,bg:blue
+    separator=fg:white,bg:cyan
+    help=path
+    frame=fg:white,bg:cyan
+    body=fg:white,bg:blue
+    edit=fg:black,bg:white
+    """
+}
+
+_NAMED_COLORS = {
+    # Basic colors
+    "black":         curses.COLOR_BLACK,
+    "red":           curses.COLOR_RED,
+    "green":         curses.COLOR_GREEN,
+    "yellow":        curses.COLOR_YELLOW,
+    "blue":          curses.COLOR_BLUE,
+    "magenta":       curses.COLOR_MAGENTA,
+    "cyan":          curses.COLOR_CYAN,
+    "white":         curses.COLOR_WHITE,
+
+    # Bright versions
+    "brightblack":   curses.COLOR_BLACK + 8,
+    "brightred":     curses.COLOR_RED + 8,
+    "brightgreen":   curses.COLOR_GREEN + 8,
+    "brightyellow":  curses.COLOR_YELLOW + 8,
+    "brightblue":    curses.COLOR_BLUE + 8,
+    "brightmagenta": curses.COLOR_MAGENTA + 8,
+    "brightcyan":    curses.COLOR_CYAN + 8,
+    "brightwhite":   curses.COLOR_WHITE + 8,
+
+    # Aliases
+    "purple":        curses.COLOR_MAGENTA,
+    "brightpurple":  curses.COLOR_MAGENTA + 8,
+}
+
+
+def _rgb_to_6cube(rgb):
+    # Converts an 888 RGB color to a 3-tuple (nice in that it's hashable)
+    # representing the closest xterm 256-color 6x6x6 color cube color.
+    #
+    # The xterm 256-color extension uses a RGB color palette with components in
+    # the range 0-5 (a 6x6x6 cube). The catch is that the mapping is nonlinear.
+    # Index 0 in the 6x6x6 cube is mapped to 0, index 1 to 95, then 135, 175,
+    # etc., in increments of 40. See the links below:
+    #
+    #   https://commons.wikimedia.org/wiki/File:Xterm_256color_chart.svg
+    #   https://github.com/tmux/tmux/blob/master/colour.c
+
+    # 48 is the middle ground between 0 and 95.
+    return tuple(0 if x < 48 else int(round(max(1, (x - 55)/40))) for x in rgb)
+
+
+def _6cube_to_rgb(r6g6b6):
+    # Returns the 888 RGB color for a 666 xterm color cube index
+
+    return tuple(0 if x == 0 else 40*x + 55 for x in r6g6b6)
+
+
+def _rgb_to_gray(rgb):
+    # Converts an 888 RGB color to the index of an xterm 256-color grayscale
+    # color with approx. the same perceived brightness
+
+    # Calculate the luminance (gray intensity) of the color. See
+    #   https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
+    # and
+    #   https://www.w3.org/TR/AERT/#color-contrast
+    luma = 0.299*rgb[0] + 0.587*rgb[1] + 0.114*rgb[2]
+
+    # Closest index in the grayscale palette, which starts at RGB 0x080808,
+    # with stepping 0x0A0A0A
+    index = int(round((luma - 8)/10))
+
+    # Clamp the index to 0-23, corresponding to 232-255
+    return max(0, min(index, 23))
+
+
+def _gray_to_rgb(index):
+    # Convert a grayscale index to its closet single RGB component
+
+    return 3*(10*index + 8,)  # Returns a 3-tuple
+
+
+# Obscure Python: We never pass a value for rgb2index, and it keeps pointing to
+# the same dict. This avoids a global.
+def _alloc_rgb(rgb, rgb2index={}):
+    # Initialize a new entry in the xterm palette to the given RGB color,
+    # returning its index. If the color has already been initialized, the index
+    # of the existing entry is returned.
+    #
+    # ncurses is palette-based, so we need to overwrite palette entries to make
+    # new colors.
+    #
+    # The colors from 0 to 15 are user-defined, and there's no way to query
+    # their RGB values, so we better leave them untouched. Also leave any
+    # hypothetical colors above 255 untouched (though we're unlikely to
+    # allocate that many colors anyway).
+
+    if rgb in rgb2index:
+        return rgb2index[rgb]
+
+    # Many terminals allow the user to customize the first 16 colors. Avoid
+    # changing their values.
+    color_index = 16 + len(rgb2index)
+    if color_index >= 256:
+        _warn("Unable to allocate new RGB color ", rgb, ". Too many colors "
+              "allocated.")
+        return 0
+
+    # Map each RGB component from the range 0-255 to the range 0-1000, which is
+    # what curses uses
+    curses.init_color(color_index, *(int(round(1000*x/255)) for x in rgb))
+    rgb2index[rgb] = color_index
+
+    return color_index
+
+
+def _color_from_num(num):
+    # Returns the index of a color that looks like color 'num' in the xterm
+    # 256-color palette (but that might not be 'num', if we're redefining
+    # colors)
+
+    # - _alloc_rgb() won't touch the first 16 colors or any (hypothetical)
+    #   colors above 255, so we can always return them as-is
+    #
+    # - If the terminal doesn't support changing color definitions, or if
+    #   curses.COLORS < 256, _alloc_rgb() won't touch any color, and all colors
+    #   can be returned as-is
+    if num < 16 or num > 255 or not curses.can_change_color() or \
+       curses.COLORS < 256:
+        return num
+
+    # _alloc_rgb() might redefine colors, so emulate the xterm 256-color
+    # palette by allocating new colors instead of returning color numbers
+    # directly
+
+    if num < 232:
+        num -= 16
+        return _alloc_rgb(_6cube_to_rgb(((num//36)%6, (num//6)%6, num%6)))
+
+    return _alloc_rgb(_gray_to_rgb(num - 232))
+
+
+def _color_from_rgb(rgb):
+    # Returns the index of a color matching the 888 RGB color 'rgb'. The
+    # returned color might be an ~exact match or an approximation, depending on
+    # terminal capabilities.
+
+    # Calculates the Euclidean distance between two RGB colors
+    def dist(r1, r2): return sum((x - y)**2 for x, y in zip(r1, r2))
+
+    if curses.COLORS >= 256:
+        # Assume we're dealing with xterm's 256-color extension
+
+        if curses.can_change_color():
+            # Best case -- the terminal supports changing palette entries via
+            # curses.init_color(). Initialize an unused palette entry and
+            # return it.
+            return _alloc_rgb(rgb)
+
+        # Second best case -- pick between the xterm 256-color extension colors
+
+        # Closest 6-cube "color" color
+        c6 = _rgb_to_6cube(rgb)
+        # Closest gray color
+        gray = _rgb_to_gray(rgb)
+
+        if dist(rgb, _6cube_to_rgb(c6)) < dist(rgb, _gray_to_rgb(gray)):
+            # Use the "color" color from the 6x6x6 color palette. Calculate the
+            # color number from the 6-cube index triplet.
+            return 16 + 36*c6[0] + 6*c6[1] + c6[2]
+
+        # Use the color from the gray palette
+        return 232 + gray
+
+    # Terminal not in xterm 256-color mode. This is probably the best we can
+    # do, or is it? Submit patches. :)
+    min_dist = float('inf')
+    best = -1
+    for color in range(curses.COLORS):
+        # ncurses uses the range 0..1000. Scale that down to 0..255.
+        d = dist(rgb, tuple(int(round(255*c/1000))
+                            for c in curses.color_content(color)))
+        if d < min_dist:
+            min_dist = d
+            best = color
+
+    return best
+
+
+def _parse_style(style_str, parsing_default):
+    # Parses a string with '<element>=<style>' assignments. Anything not
+    # containing '=' is assumed to be a reference to a built-in style, which is
+    # treated as if all the assignments from the style were inserted at that
+    # point in the string.
+    #
+    # The parsing_default flag is set to True when we're implicitly parsing the
+    # 'default'/'monochrome' style, to prevent warnings.
+
+    for sline in style_str.split():
+        # Words without a "=" character represents a style template
+        if "=" in sline:
+            key, data = sline.split("=", 1)
+
+            # The 'default' style template is assumed to define all keys. We
+            # run _style_to_curses() for non-existing keys as well, so that we
+            # print warnings for errors to the right of '=' for those too.
+            if key not in _style and not parsing_default:
+                _warn("Ignoring non-existent style", key)
+
+            # If data is a reference to another key, copy its style
+            if data in _style:
+                _style[key] = _style[data]
+            else:
+                _style[key] = _style_to_curses(data)
+
+        elif sline in _STYLES:
+            # Recursively parse style template. Ignore styles that don't exist,
+            # for backwards/forwards compatibility.
+            _parse_style(_STYLES[sline], parsing_default)
+
+        else:
+            _warn("Ignoring non-existent style template", sline)
+
+# Dictionary mapping element types to the curses attributes used to display
+# them
+_style = {}
+
+
+def _style_to_curses(style_def):
+    # Parses a style definition string (<element>=<style>), returning
+    # a (fg_color, bg_color, attributes) tuple.
+
+    def parse_color(color_def):
+        color_def = color_def.split(":", 1)[1]
+
+        # HTML format, #RRGGBB
+        if re.match("#[A-Fa-f0-9]{6}", color_def):
+            return _color_from_rgb((
+                int(color_def[1:3], 16),
+                int(color_def[3:5], 16),
+                int(color_def[5:7], 16)))
+
+        if color_def in _NAMED_COLORS:
+            color_num = _color_from_num(_NAMED_COLORS[color_def])
+        else:
+            try:
+                color_num = _color_from_num(int(color_def, 0))
+            except ValueError:
+                _warn("Ignoring color", color_def, "that's neither "
+                      "predefined nor a number")
+                return -1
+
+        if not -1 <= color_num < curses.COLORS:
+            _warn("Ignoring color {}, which is outside the range "
+                  "-1..curses.COLORS-1 (-1..{})"
+                  .format(color_def, curses.COLORS - 1))
+            return -1
+
+        return color_num
+
+    fg_color = -1
+    bg_color = -1
+    attrs = 0
+
+    if style_def:
+        for field in style_def.split(","):
+            if field.startswith("fg:"):
+                fg_color = parse_color(field)
+            elif field.startswith("bg:"):
+                bg_color = parse_color(field)
+            elif field == "bold":
+                # A_BOLD tends to produce faint and hard-to-read text on the
+                # Windows console, especially with the old color scheme, before
+                # the introduction of
+                # https://blogs.msdn.microsoft.com/commandline/2017/08/02/updating-the-windows-console-colors/
+                attrs |= curses.A_NORMAL if _IS_WINDOWS else curses.A_BOLD
+            elif field == "standout":
+                attrs |= curses.A_STANDOUT
+            elif field == "underline":
+                attrs |= curses.A_UNDERLINE
+            else:
+                _warn("Ignoring unknown style attribute", field)
+
+    return _style_attr(fg_color, bg_color, attrs)
+
+
+def _init_styles():
+    if curses.has_colors():
+        try:
+            curses.use_default_colors()
+        except curses.error:
+            # Ignore errors on funky terminals that support colors but not
+            # using default colors. Worst it can do is break transparency and
+            # the like. Ran across this with the MSYS2/winpty setup in
+            # https://github.com/msys2/MINGW-packages/issues/5823, though there
+            # seems to be a lot of general brokenness there.
+            pass
+
+        # Use the 'default' theme as the base, and add any user-defined style
+        # settings from the environment
+        _parse_style("default", True)
+        if "MENUCONFIG_STYLE" in os.environ:
+            _parse_style(os.environ["MENUCONFIG_STYLE"], False)
+    else:
+        # Force the 'monochrome' theme if the terminal doesn't support colors.
+        # MENUCONFIG_STYLE is likely to mess things up here (though any colors
+        # would be ignored), so ignore it.
+        _parse_style("monochrome", True)
+
+
+# color_attribs holds the color pairs we've already created, indexed by a
+# (<foreground color>, <background color>) tuple.
+#
+# Obscure Python: We never pass a value for color_attribs, and it keeps
+# pointing to the same dict. This avoids a global.
+def _style_attr(fg_color, bg_color, attribs, color_attribs={}):
+    # Returns an attribute with the specified foreground and background color
+    # and the attributes in 'attribs'. Reuses color pairs already created if
+    # possible, and creates a new color pair otherwise.
+    #
+    # Returns 'attribs' if colors aren't supported.
+
+    if not curses.has_colors():
+        return attribs
+
+    if (fg_color, bg_color) not in color_attribs:
+        # Create new color pair. Color pair number 0 is hardcoded and cannot be
+        # changed, hence the +1s.
+        curses.init_pair(len(color_attribs) + 1, fg_color, bg_color)
+        color_attribs[(fg_color, bg_color)] = \
+            curses.color_pair(len(color_attribs) + 1)
+
+    return color_attribs[(fg_color, bg_color)] | attribs
+
+
+#
+# Main application
+#
+
+
+def _main():
+    menuconfig(standard_kconfig(__doc__))
+
+
+def menuconfig(kconf):
+    """
+    Launches the configuration interface, returning after the user exits.
+
+    kconf:
+      Kconfig instance to be configured
+    """
+    global _kconf
+    global _conf_filename
+    global _conf_changed
+    global _minconf_filename
+    global _show_all
+
+    _kconf = kconf
+
+    # Filename to save configuration to
+    _conf_filename = standard_config_filename()
+
+    # Load existing configuration and set _conf_changed True if it is outdated
+    _conf_changed = _load_config()
+
+    # Filename to save minimal configuration to
+    _minconf_filename = "defconfig"
+
+    # Any visible items in the top menu?
+    _show_all = False
+    if not _shown_nodes(kconf.top_node):
+        # Nothing visible. Start in show-all mode and try again.
+        _show_all = True
+        if not _shown_nodes(kconf.top_node):
+            # Give up. The implementation relies on always having a selected
+            # node.
+            print("Empty configuration -- nothing to configure.\n"
+                  "Check that environment variables are set properly.")
+            return
+
+    # Disable warnings. They get mangled in curses mode, and we deal with
+    # errors ourselves.
+    kconf.warn = False
+
+    # Make curses use the locale settings specified in the environment
+    locale.setlocale(locale.LC_ALL, "")
+
+    # Try to fix Unicode issues on systems with bad defaults
+    if _CHANGE_C_LC_CTYPE_TO_UTF8:
+        _change_c_lc_ctype_to_utf8()
+
+    # Get rid of the delay between pressing ESC and jumping to the parent menu,
+    # unless the user has set ESCDELAY (see ncurses(3)). This makes the UI much
+    # smoother to work with.
+    #
+    # Note: This is strictly pretty iffy, since escape codes for e.g. cursor
+    # keys start with ESC, but I've never seen it cause problems in practice
+    # (probably because it's unlikely that the escape code for a key would get
+    # split up across read()s, at least with a terminal emulator). Please
+    # report if you run into issues. Some suitable small default value could be
+    # used here instead in that case. Maybe it's silly to not put in the
+    # smallest imperceptible delay here already, though I don't like guessing.
+    #
+    # (From a quick glance at the ncurses source code, ESCDELAY might only be
+    # relevant for mouse events there, so maybe escapes are assumed to arrive
+    # in one piece already...)
+    os.environ.setdefault("ESCDELAY", "0")
+
+    # Enter curses mode. _menuconfig() returns a string to print on exit, after
+    # curses has been de-initialized.
+    print(curses.wrapper(_menuconfig))
+
+
+def _load_config():
+    # Loads any existing .config file. See the Kconfig.load_config() docstring.
+    #
+    # Returns True if .config is missing or outdated. We always prompt for
+    # saving the configuration in that case.
+
+    print(_kconf.load_config())
+    if not os.path.exists(_conf_filename):
+        # No .config
+        return True
+
+    return _needs_save()
+
+
+def _needs_save():
+    # Returns True if a just-loaded .config file is outdated (would get
+    # modified when saving)
+
+    if _kconf.missing_syms:
+        # Assignments to undefined symbols in the .config
+        return True
+
+    for sym in _kconf.unique_defined_syms:
+        if sym.user_value is None:
+            if sym.config_string:
+                # Unwritten symbol
+                return True
+        elif sym.orig_type in (BOOL, TRISTATE):
+            if sym.tri_value != sym.user_value:
+                # Written bool/tristate symbol, new value
+                return True
+        elif sym.str_value != sym.user_value:
+            # Written string/int/hex symbol, new value
+            return True
+
+    # No need to prompt for save
+    return False
+
+
+# Global variables used below:
+#
+#   _stdscr:
+#     stdscr from curses
+#
+#   _cur_menu:
+#     Menu node of the menu (or menuconfig symbol, or choice) currently being
+#     shown
+#
+#   _shown:
+#     List of items in _cur_menu that are shown (ignoring scrolling). In
+#     show-all mode, this list contains all items in _cur_menu. Otherwise, it
+#     contains just the visible items.
+#
+#   _sel_node_i:
+#     Index in _shown of the currently selected node
+#
+#   _menu_scroll:
+#     Index in _shown of the top row of the main display
+#
+#   _parent_screen_rows:
+#     List/stack of the row numbers that the selections in the parent menus
+#     appeared on. This is used to prevent the scrolling from jumping around
+#     when going in and out of menus.
+#
+#   _show_help/_show_name/_show_all:
+#     If True, the corresponding mode is on. See the module docstring.
+#
+#   _conf_filename:
+#     File to save the configuration to
+#
+#   _minconf_filename:
+#     File to save minimal configurations to
+#
+#   _conf_changed:
+#     True if the configuration has been changed. If False, we don't bother
+#     showing the save-and-quit dialog.
+#
+#     We reset this to False whenever the configuration is saved explicitly
+#     from the save dialog.
+
+
+def _menuconfig(stdscr):
+    # Logic for the main display, with the list of symbols, etc.
+
+    global _stdscr
+    global _conf_filename
+    global _conf_changed
+    global _minconf_filename
+    global _show_help
+    global _show_name
+
+    _stdscr = stdscr
+
+    _init()
+
+    while True:
+        _draw_main()
+        curses.doupdate()
+
+
+        c = _getch_compat(_menu_win)
+
+        if c == curses.KEY_RESIZE:
+            _resize_main()
+
+        elif c in (curses.KEY_DOWN, "j", "J"):
+            _select_next_menu_entry()
+
+        elif c in (curses.KEY_UP, "k", "K"):
+            _select_prev_menu_entry()
+
+        elif c in (curses.KEY_NPAGE, "\x04"):  # Page Down/Ctrl-D
+            # Keep it simple. This way we get sane behavior for small windows,
+            # etc., for free.
+            for _ in range(_PG_JUMP):
+                _select_next_menu_entry()
+
+        elif c in (curses.KEY_PPAGE, "\x15"):  # Page Up/Ctrl-U
+            for _ in range(_PG_JUMP):
+                _select_prev_menu_entry()
+
+        elif c in (curses.KEY_END, "G"):
+            _select_last_menu_entry()
+
+        elif c in (curses.KEY_HOME, "g"):
+            _select_first_menu_entry()
+
+        elif c == " ":
+            # Toggle the node if possible
+            sel_node = _shown[_sel_node_i]
+            if not _change_node(sel_node):
+                _enter_menu(sel_node)
+
+        elif c in (curses.KEY_RIGHT, "\n", "l", "L"):
+            # Enter the node if possible
+            sel_node = _shown[_sel_node_i]
+            if not _enter_menu(sel_node):
+                _change_node(sel_node)
+
+        elif c in ("n", "N"):
+            _set_sel_node_tri_val(0)
+
+        elif c in ("m", "M"):
+            _set_sel_node_tri_val(1)
+
+        elif c in ("y", "Y"):
+            _set_sel_node_tri_val(2)
+
+        elif c in (curses.KEY_LEFT, curses.KEY_BACKSPACE, _ERASE_CHAR,
+                   "\x1B", "h", "H"):  # \x1B = ESC
+
+            if c == "\x1B" and _cur_menu is _kconf.top_node:
+                res = _quit_dialog()
+                if res:
+                    return res
+            else:
+                _leave_menu()
+
+        elif c in ("o", "O"):
+            _load_dialog()
+
+        elif c in ("s", "S"):
+            filename = _save_dialog(_kconf.write_config, _conf_filename,
+                                    "configuration")
+            if filename:
+                _conf_filename = filename
+                _conf_changed = False
+
+        elif c in ("d", "D"):
+            filename = _save_dialog(_kconf.write_min_config, _minconf_filename,
+                                    "minimal configuration")
+            if filename:
+                _minconf_filename = filename
+
+        elif c == "/":
+            _jump_to_dialog()
+            # The terminal might have been resized while the fullscreen jump-to
+            # dialog was open
+            _resize_main()
+
+        elif c == "?":
+            _info_dialog(_shown[_sel_node_i], False)
+            # The terminal might have been resized while the fullscreen info
+            # dialog was open
+            _resize_main()
+
+        elif c in ("f", "F"):
+            _show_help = not _show_help
+            _set_style(_help_win, "show-help" if _show_help else "help")
+            _resize_main()
+
+        elif c in ("c", "C"):
+            _show_name = not _show_name
+
+        elif c in ("a", "A"):
+            _toggle_show_all()
+
+        elif c in ("q", "Q"):
+            res = _quit_dialog()
+            if res:
+                return res
+
+
+def _quit_dialog():
+    if not _conf_changed:
+        return "No changes to save (for '{}')".format(_conf_filename)
+
+    while True:
+        c = _key_dialog(
+            "Quit",
+            " Save configuration?\n"
+            "\n"
+            "(Y)es  (N)o  (C)ancel",
+            "ync")
+
+        if c is None or c == "c":
+            return None
+
+        if c == "y":
+            # Returns a message to print
+            msg = _try_save(_kconf.write_config, _conf_filename, "configuration")
+            if msg:
+                return msg
+
+        elif c == "n":
+            return "Configuration ({}) was not saved".format(_conf_filename)
+
+
+def _init():
+    # Initializes the main display with the list of symbols, etc. Also does
+    # misc. global initialization that needs to happen after initializing
+    # curses.
+
+    global _ERASE_CHAR
+
+    global _path_win
+    global _top_sep_win
+    global _menu_win
+    global _bot_sep_win
+    global _help_win
+
+    global _parent_screen_rows
+    global _cur_menu
+    global _shown
+    global _sel_node_i
+    global _menu_scroll
+
+    global _show_help
+    global _show_name
+
+    # Looking for this in addition to KEY_BACKSPACE (which is unreliable) makes
+    # backspace work with TERM=vt100. That makes it likely to work in sane
+    # environments.
+    _ERASE_CHAR = curses.erasechar()
+    if sys.version_info[0] >= 3:
+        # erasechar() returns a one-byte bytes object on Python 3. This sets
+        # _ERASE_CHAR to a blank string if it can't be decoded, which should be
+        # harmless.
+        _ERASE_CHAR = _ERASE_CHAR.decode("utf-8", "ignore")
+
+    _init_styles()
+
+    # Hide the cursor
+    _safe_curs_set(0)
+
+    # Initialize windows
+
+    # Top row, with menu path
+    _path_win = _styled_win("path")
+
+    # Separator below menu path, with title and arrows pointing up
+    _top_sep_win = _styled_win("separator")
+
+    # List of menu entries with symbols, etc.
+    _menu_win = _styled_win("list")
+    _menu_win.keypad(True)
+
+    # Row below menu list, with arrows pointing down
+    _bot_sep_win = _styled_win("separator")
+
+    # Help window with keys at the bottom. Shows help texts in show-help mode.
+    _help_win = _styled_win("help")
+
+    # The rows we'd like the nodes in the parent menus to appear on. This
+    # prevents the scroll from jumping around when going in and out of menus.
+    _parent_screen_rows = []
+
+    # Initial state
+
+    _cur_menu = _kconf.top_node
+    _shown = _shown_nodes(_cur_menu)
+    _sel_node_i = _menu_scroll = 0
+
+    _show_help = _show_name = False
+
+    # Give windows their initial size
+    _resize_main()
+
+
+def _resize_main():
+    # Resizes the main display, with the list of symbols, etc., to fill the
+    # terminal
+
+    global _menu_scroll
+
+    screen_height, screen_width = _stdscr.getmaxyx()
+
+    _path_win.resize(1, screen_width)
+    _top_sep_win.resize(1, screen_width)
+    _bot_sep_win.resize(1, screen_width)
+
+    help_win_height = _SHOW_HELP_HEIGHT if _show_help else \
+        len(_MAIN_HELP_LINES)
+
+    menu_win_height = screen_height - help_win_height - 3
+
+    if menu_win_height >= 1:
+        _menu_win.resize(menu_win_height, screen_width)
+        _help_win.resize(help_win_height, screen_width)
+
+        _top_sep_win.mvwin(1, 0)
+        _menu_win.mvwin(2, 0)
+        _bot_sep_win.mvwin(2 + menu_win_height, 0)
+        _help_win.mvwin(2 + menu_win_height + 1, 0)
+    else:
+        # Degenerate case. Give up on nice rendering and just prevent errors.
+
+        menu_win_height = 1
+
+        _menu_win.resize(1, screen_width)
+        _help_win.resize(1, screen_width)
+
+        for win in _top_sep_win, _menu_win, _bot_sep_win, _help_win:
+            win.mvwin(0, 0)
+
+    # Adjust the scroll so that the selected node is still within the window,
+    # if needed
+    if _sel_node_i - _menu_scroll >= menu_win_height:
+        _menu_scroll = _sel_node_i - menu_win_height + 1
+
+
+def _height(win):
+    # Returns the height of 'win'
+
+    return win.getmaxyx()[0]
+
+
+def _width(win):
+    # Returns the width of 'win'
+
+    return win.getmaxyx()[1]
+
+
+def _enter_menu(menu):
+    # Makes 'menu' the currently displayed menu. In addition to actual 'menu's,
+    # "menu" here includes choices and symbols defined with the 'menuconfig'
+    # keyword.
+    #
+    # Returns False if 'menu' can't be entered.
+
+    global _cur_menu
+    global _shown
+    global _sel_node_i
+    global _menu_scroll
+
+    if not menu.is_menuconfig:
+        return False  # Not a menu
+
+    shown_sub = _shown_nodes(menu)
+    # Never enter empty menus. We depend on having a current node.
+    if not shown_sub:
+        return False
+
+    # Remember where the current node appears on the screen, so we can try
+    # to get it to appear in the same place when we leave the menu
+    _parent_screen_rows.append(_sel_node_i - _menu_scroll)
+
+    # Jump into menu
+    _cur_menu = menu
+    _shown = shown_sub
+    _sel_node_i = _menu_scroll = 0
+
+    if isinstance(menu.item, Choice):
+        _select_selected_choice_sym()
+
+    return True
+
+
+def _select_selected_choice_sym():
+    # Puts the cursor on the currently selected (y-valued) choice symbol, if
+    # any. Does nothing if if the choice has no selection (is not visible/in y
+    # mode).
+
+    global _sel_node_i
+
+    choice = _cur_menu.item
+    if choice.selection:
+        # Search through all menu nodes to handle choice symbols being defined
+        # in multiple locations
+        for node in choice.selection.nodes:
+            if node in _shown:
+                _sel_node_i = _shown.index(node)
+                _center_vertically()
+                return
+
+
+def _jump_to(node):
+    # Jumps directly to the menu node 'node'
+
+    global _cur_menu
+    global _shown
+    global _sel_node_i
+    global _menu_scroll
+    global _show_all
+    global _parent_screen_rows
+
+    # Clear remembered menu locations. We might not even have been in the
+    # parent menus before.
+    _parent_screen_rows = []
+
+    old_show_all = _show_all
+    jump_into = (isinstance(node.item, Choice) or node.item == MENU) and \
+                node.list
+
+    # If we're jumping to a non-empty choice or menu, jump to the first entry
+    # in it instead of jumping to its menu node
+    if jump_into:
+        _cur_menu = node
+        node = node.list
+    else:
+        _cur_menu = _parent_menu(node)
+
+    _shown = _shown_nodes(_cur_menu)
+    if node not in _shown:
+        # The node wouldn't be shown. Turn on show-all to show it.
+        _show_all = True
+        _shown = _shown_nodes(_cur_menu)
+
+    _sel_node_i = _shown.index(node)
+
+    if jump_into and not old_show_all and _show_all:
+        # If we're jumping into a choice or menu and were forced to turn on
+        # show-all because the first entry wasn't visible, try turning it off.
+        # That will land us at the first visible node if there are visible
+        # nodes, and is a no-op otherwise.
+        _toggle_show_all()
+
+    _center_vertically()
+
+    # If we're jumping to a non-empty choice, jump to the selected symbol, if
+    # any
+    if jump_into and isinstance(_cur_menu.item, Choice):
+        _select_selected_choice_sym()
+
+
+def _leave_menu():
+    # Jumps to the parent menu of the current menu. Does nothing if we're in
+    # the top menu.
+
+    global _cur_menu
+    global _shown
+    global _sel_node_i
+    global _menu_scroll
+
+    if _cur_menu is _kconf.top_node:
+        return
+
+    # Jump to parent menu
+    parent = _parent_menu(_cur_menu)
+    _shown = _shown_nodes(parent)
+    _sel_node_i = _shown.index(_cur_menu)
+    _cur_menu = parent
+
+    # Try to make the menu entry appear on the same row on the screen as it did
+    # before we entered the menu.
+
+    if _parent_screen_rows:
+        # The terminal might have shrunk since we were last in the parent menu
+        screen_row = min(_parent_screen_rows.pop(), _height(_menu_win) - 1)
+        _menu_scroll = max(_sel_node_i - screen_row, 0)
+    else:
+        # No saved parent menu locations, meaning we jumped directly to some
+        # node earlier
+        _center_vertically()
+
+
+def _select_next_menu_entry():
+    # Selects the menu entry after the current one, adjusting the scroll if
+    # necessary. Does nothing if we're already at the last menu entry.
+
+    global _sel_node_i
+    global _menu_scroll
+
+    if _sel_node_i < len(_shown) - 1:
+        # Jump to the next node
+        _sel_node_i += 1
+
+        # If the new node is sufficiently close to the edge of the menu window
+        # (as determined by _SCROLL_OFFSET), increase the scroll by one. This
+        # gives nice and non-jumpy behavior even when
+        # _SCROLL_OFFSET >= _height(_menu_win).
+        if _sel_node_i >= _menu_scroll + _height(_menu_win) - _SCROLL_OFFSET \
+           and _menu_scroll < _max_scroll(_shown, _menu_win):
+
+            _menu_scroll += 1
+
+
+def _select_prev_menu_entry():
+    # Selects the menu entry before the current one, adjusting the scroll if
+    # necessary. Does nothing if we're already at the first menu entry.
+
+    global _sel_node_i
+    global _menu_scroll
+
+    if _sel_node_i > 0:
+        # Jump to the previous node
+        _sel_node_i -= 1
+
+        # See _select_next_menu_entry()
+        if _sel_node_i < _menu_scroll + _SCROLL_OFFSET:
+            _menu_scroll = max(_menu_scroll - 1, 0)
+
+
+def _select_last_menu_entry():
+    # Selects the last menu entry in the current menu
+
+    global _sel_node_i
+    global _menu_scroll
+
+    _sel_node_i = len(_shown) - 1
+    _menu_scroll = _max_scroll(_shown, _menu_win)
+
+
+def _select_first_menu_entry():
+    # Selects the first menu entry in the current menu
+
+    global _sel_node_i
+    global _menu_scroll
+
+    _sel_node_i = _menu_scroll = 0
+
+
+def _toggle_show_all():
+    # Toggles show-all mode on/off. If turning it off would give no visible
+    # items in the current menu, it is left on.
+
+    global _show_all
+    global _shown
+    global _sel_node_i
+    global _menu_scroll
+
+    # Row on the screen the cursor is on. Preferably we want the same row to
+    # stay highlighted.
+    old_row = _sel_node_i - _menu_scroll
+
+    _show_all = not _show_all
+    # List of new nodes to be shown after toggling _show_all
+    new_shown = _shown_nodes(_cur_menu)
+
+    # Find a good node to select. The selected node might disappear if show-all
+    # mode is turned off.
+
+    # Select the previously selected node itself if it is still visible. If
+    # there are visible nodes before it, select the closest one.
+    for node in _shown[_sel_node_i::-1]:
+        if node in new_shown:
+            _sel_node_i = new_shown.index(node)
+            break
+    else:
+        # No visible nodes before the previously selected node. Select the
+        # closest visible node after it instead.
+        for node in _shown[_sel_node_i + 1:]:
+            if node in new_shown:
+                _sel_node_i = new_shown.index(node)
+                break
+        else:
+            # No visible nodes at all, meaning show-all was turned off inside
+            # an invisible menu. Don't allow that, as the implementation relies
+            # on always having a selected node.
+            _show_all = True
+            return
+
+    _shown = new_shown
+
+    # Try to make the cursor stay on the same row in the menu window. This
+    # might be impossible if too many nodes have disappeared above the node.
+    _menu_scroll = max(_sel_node_i - old_row, 0)
+
+
+def _center_vertically():
+    # Centers the selected node vertically, if possible
+
+    global _menu_scroll
+
+    _menu_scroll = min(max(_sel_node_i - _height(_menu_win)//2, 0),
+                       _max_scroll(_shown, _menu_win))
+
+
+def _draw_main():
+    # Draws the "main" display, with the list of symbols, the header, and the
+    # footer.
+    #
+    # This could be optimized to only update the windows that have actually
+    # changed, but keep it simple for now and let curses sort it out.
+
+    term_width = _width(_stdscr)
+
+    #
+    # Update the separator row below the menu path
+    #
+
+    _top_sep_win.erase()
+
+    # Draw arrows pointing up if the symbol window is scrolled down. Draw them
+    # before drawing the title, so the title ends up on top for small windows.
+    if _menu_scroll > 0:
+        _safe_hline(_top_sep_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
+
+    # Add the 'mainmenu' text as the title, centered at the top
+    _safe_addstr(_top_sep_win,
+                 0, max((term_width - len(_kconf.mainmenu_text))//2, 0),
+                 _kconf.mainmenu_text)
+
+    _top_sep_win.noutrefresh()
+
+    # Note: The menu path at the top is deliberately updated last. See below.
+
+    #
+    # Update the symbol window
+    #
+
+    _menu_win.erase()
+
+    # Draw the _shown nodes starting from index _menu_scroll up to either as
+    # many as fit in the window, or to the end of _shown
+    for i in range(_menu_scroll,
+                   min(_menu_scroll + _height(_menu_win), len(_shown))):
+
+        node = _shown[i]
+
+        # The 'not _show_all' test avoids showing invisible items in red
+        # outside show-all mode, which could look confusing/broken. Invisible
+        # symbols show up outside show-all mode if an invisible symbol has
+        # visible children in an implicit (indented) menu.
+        if _visible(node) or not _show_all:
+            style = _style["selection" if i == _sel_node_i else "list"]
+        else:
+            style = _style["inv-selection" if i == _sel_node_i else "inv-list"]
+
+        _safe_addstr(_menu_win, i - _menu_scroll, 0, _node_str(node), style)
+
+    _menu_win.noutrefresh()
+
+    #
+    # Update the bottom separator window
+    #
+
+    _bot_sep_win.erase()
+
+    # Draw arrows pointing down if the symbol window is scrolled up
+    if _menu_scroll < _max_scroll(_shown, _menu_win):
+        _safe_hline(_bot_sep_win, 0, 4, curses.ACS_DARROW, _N_SCROLL_ARROWS)
+
+    # Indicate when show-name/show-help/show-all mode is enabled
+    enabled_modes = []
+    if _show_help:
+        enabled_modes.append("show-help (toggle with [F])")
+    if _show_name:
+        enabled_modes.append("show-name")
+    if _show_all:
+        enabled_modes.append("show-all")
+    if enabled_modes:
+        s = " and ".join(enabled_modes) + " mode enabled"
+        _safe_addstr(_bot_sep_win, 0, max(term_width - len(s) - 2, 0), s)
+
+    _bot_sep_win.noutrefresh()
+
+    #
+    # Update the help window, which shows either key bindings or help texts
+    #
+
+    _help_win.erase()
+
+    if _show_help:
+        node = _shown[_sel_node_i]
+        if isinstance(node.item, (Symbol, Choice)) and node.help:
+            help_lines = textwrap.wrap(node.help, _width(_help_win))
+            for i in range(min(_height(_help_win), len(help_lines))):
+                _safe_addstr(_help_win, i, 0, help_lines[i])
+        else:
+            _safe_addstr(_help_win, 0, 0, "(no help)")
+    else:
+        for i, line in enumerate(_MAIN_HELP_LINES):
+            _safe_addstr(_help_win, i, 0, line)
+
+    _help_win.noutrefresh()
+
+    #
+    # Update the top row with the menu path.
+    #
+    # Doing this last leaves the cursor on the top row, which avoids some minor
+    # annoying jumpiness in gnome-terminal when reducing the height of the
+    # terminal. It seems to happen whenever the row with the cursor on it
+    # disappears.
+    #
+
+    _path_win.erase()
+
+    # Draw the menu path ("(Top) -> Menu -> Submenu -> ...")
+
+    menu_prompts = []
+
+    menu = _cur_menu
+    while menu is not _kconf.top_node:
+        # Promptless choices can be entered in show-all mode. Use
+        # standard_sc_expr_str() for them, so they show up as
+        # '<choice (name if any)>'.
+        menu_prompts.append(menu.prompt[0] if menu.prompt else
+                            standard_sc_expr_str(menu.item))
+        menu = menu.parent
+    menu_prompts.append("(Top)")
+    menu_prompts.reverse()
+
+    # Hack: We can't put ACS_RARROW directly in the string. Temporarily
+    # represent it with NULL.
+    menu_path_str = " \0 ".join(menu_prompts)
+
+    # Scroll the menu path to the right if needed to make the current menu's
+    # title visible
+    if len(menu_path_str) > term_width:
+        menu_path_str = menu_path_str[len(menu_path_str) - term_width:]
+
+    # Print the path with the arrows reinserted
+    split_path = menu_path_str.split("\0")
+    _safe_addstr(_path_win, split_path[0])
+    for s in split_path[1:]:
+        _safe_addch(_path_win, curses.ACS_RARROW)
+        _safe_addstr(_path_win, s)
+
+    _path_win.noutrefresh()
+
+
+def _parent_menu(node):
+    # Returns the menu node of the menu that contains 'node'. In addition to
+    # proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'.
+    # "Menu" here means a menu in the interface.
+
+    menu = node.parent
+    while not menu.is_menuconfig:
+        menu = menu.parent
+    return menu
+
+
+def _shown_nodes(menu):
+    # Returns the list of menu nodes from 'menu' (see _parent_menu()) that
+    # would be shown when entering it
+
+    def rec(node):
+        res = []
+
+        while node:
+            if _visible(node) or _show_all:
+                res.append(node)
+                if node.list and not node.is_menuconfig:
+                    # Nodes from implicit menu created from dependencies. Will
+                    # be shown indented. Note that is_menuconfig is True for
+                    # menus and choices as well as 'menuconfig' symbols.
+                    res += rec(node.list)
+
+            elif node.list and isinstance(node.item, Symbol):
+                # Show invisible symbols if they have visible children. This
+                # can happen for an m/y-valued symbol with an optional prompt
+                # ('prompt "foo" is COND') that is currently disabled. Note
+                # that it applies to both 'config' and 'menuconfig' symbols.
+                shown_children = rec(node.list)
+                if shown_children:
+                    res.append(node)
+                    if not node.is_menuconfig:
+                        res += shown_children
+
+            node = node.next
+
+        return res
+
+    if isinstance(menu.item, Choice):
+        # For named choices defined in multiple locations, entering the choice
+        # at a particular menu node would normally only show the choice symbols
+        # defined there (because that's what the MenuNode tree looks like).
+        #
+        # That might look confusing, and makes extending choices by defining
+        # them in multiple locations less useful. Instead, gather all the child
+        # menu nodes for all the choices whenever a choice is entered. That
+        # makes all choice symbols visible at all locations.
+        #
+        # Choices can contain non-symbol items (people do all sorts of weird
+        # stuff with them), hence the generality here. We really need to
+        # preserve the menu tree at each choice location.
+        #
+        # Note: Named choices are pretty broken in the C tools, and this is
+        # super obscure, so you probably won't find much that relies on this.
+        # This whole 'if' could be deleted if you don't care about defining
+        # choices in multiple locations to add symbols (which will still work,
+        # just with things being displayed in a way that might be unexpected).
+
+        # Do some additional work to avoid listing choice symbols twice if all
+        # or part of the choice is copied in multiple locations (e.g. by
+        # including some Kconfig file multiple times). We give the prompts at
+        # the current location precedence.
+        seen_syms = {node.item for node in rec(menu.list)
+                     if isinstance(node.item, Symbol)}
+        res = []
+        for choice_node in menu.item.nodes:
+            for node in rec(choice_node.list):
+                # 'choice_node is menu' checks if we're dealing with the
+                # current location
+                if node.item not in seen_syms or choice_node is menu:
+                    res.append(node)
+                    if isinstance(node.item, Symbol):
+                        seen_syms.add(node.item)
+        return res
+
+    return rec(menu.list)
+
+
+def _visible(node):
+    # Returns True if the node should appear in the menu (outside show-all
+    # mode)
+
+    return node.prompt and expr_value(node.prompt[1]) and not \
+        (node.item == MENU and not expr_value(node.visibility))
+
+
+def _change_node(node):
+    # Changes the value of the menu node 'node' if it is a symbol. Bools and
+    # tristates are toggled, while other symbol types pop up a text entry
+    # dialog.
+    #
+    # Returns False if the value of 'node' can't be changed.
+
+    if not _changeable(node):
+        return False
+
+    # sc = symbol/choice
+    sc = node.item
+
+    if sc.orig_type in (INT, HEX, STRING):
+        s = sc.str_value
+
+        while True:
+            s = _input_dialog(
+                "{} ({})".format(node.prompt[0], TYPE_TO_STR[sc.orig_type]),
+                s, _range_info(sc))
+
+            if s is None:
+                break
+
+            if sc.orig_type in (INT, HEX):
+                s = s.strip()
+
+                # 'make menuconfig' does this too. Hex values not starting with
+                # '0x' are accepted when loading .config files though.
+                if sc.orig_type == HEX and not s.startswith(("0x", "0X")):
+                    s = "0x" + s
+
+            if _check_valid(sc, s):
+                _set_val(sc, s)
+                break
+
+    elif len(sc.assignable) == 1:
+        # Handles choice symbols for choices in y mode, which are a special
+        # case: .assignable can be (2,) while .tri_value is 0.
+        _set_val(sc, sc.assignable[0])
+
+    else:
+        # Set the symbol to the value after the current value in
+        # sc.assignable, with wrapping
+        val_index = sc.assignable.index(sc.tri_value)
+        _set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)])
+
+
+    if _is_y_mode_choice_sym(sc) and not node.list:
+        # Immediately jump to the parent menu after making a choice selection,
+        # like 'make menuconfig' does, except if the menu node has children
+        # (which can happen if a symbol 'depends on' a choice symbol that
+        # immediately precedes it).
+        _leave_menu()
+
+
+    return True
+
+
+def _changeable(node):
+    # Returns True if the value if 'node' can be changed
+
+    sc = node.item
+
+    if not isinstance(sc, (Symbol, Choice)):
+        return False
+
+    # This will hit for invisible symbols, which appear in show-all mode and
+    # when an invisible symbol has visible children (which can happen e.g. for
+    # symbols with optional prompts)
+    if not (node.prompt and expr_value(node.prompt[1])):
+        return False
+
+    return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \
+        or _is_y_mode_choice_sym(sc)
+
+
+def _set_sel_node_tri_val(tri_val):
+    # Sets the value of the currently selected menu entry to 'tri_val', if that
+    # value can be assigned
+
+    sc = _shown[_sel_node_i].item
+    if isinstance(sc, (Symbol, Choice)) and tri_val in sc.assignable:
+        _set_val(sc, tri_val)
+
+
+def _set_val(sc, val):
+    # Wrapper around Symbol/Choice.set_value() for updating the menu state and
+    # _conf_changed
+
+    global _conf_changed
+
+    # Use the string representation of tristate values. This makes the format
+    # consistent for all symbol types.
+    if val in TRI_TO_STR:
+        val = TRI_TO_STR[val]
+
+    if val != sc.str_value:
+        sc.set_value(val)
+        _conf_changed = True
+
+        # Changing the value of the symbol might have changed what items in the
+        # current menu are visible. Recalculate the state.
+        _update_menu()
+
+
+def _update_menu():
+    # Updates the current menu after the value of a symbol or choice has been
+    # changed. Changing a value might change which items in the menu are
+    # visible.
+    #
+    # If possible, preserves the location of the cursor on the screen when
+    # items are added/removed above the selected item.
+
+    global _shown
+    global _sel_node_i
+    global _menu_scroll
+
+    # Row on the screen the cursor was on
+    old_row = _sel_node_i - _menu_scroll
+
+    sel_node = _shown[_sel_node_i]
+
+    # New visible nodes
+    _shown = _shown_nodes(_cur_menu)
+
+    # New index of selected node
+    _sel_node_i = _shown.index(sel_node)
+
+    # Try to make the cursor stay on the same row in the menu window. This
+    # might be impossible if too many nodes have disappeared above the node.
+    _menu_scroll = max(_sel_node_i - old_row, 0)
+
+
+def _input_dialog(title, initial_text, info_text=None):
+    # Pops up a dialog that prompts the user for a string
+    #
+    # title:
+    #   Title to display at the top of the dialog window's border
+    #
+    # initial_text:
+    #   Initial text to prefill the input field with
+    #
+    # info_text:
+    #   String to show next to the input field. If None, just the input field
+    #   is shown.
+
+    win = _styled_win("body")
+    win.keypad(True)
+
+    info_lines = info_text.split("\n") if info_text else []
+
+    # Give the input dialog its initial size
+    _resize_input_dialog(win, title, info_lines)
+
+    _safe_curs_set(2)
+
+    # Input field text
+    s = initial_text
+
+    # Cursor position
+    i = len(initial_text)
+
+    def edit_width():
+        return _width(win) - 4
+
+    # Horizontal scroll offset
+    hscroll = max(i - edit_width() + 1, 0)
+
+    while True:
+        # Draw the "main" display with the menu, etc., so that resizing still
+        # works properly. This is like a stack of windows, only hardcoded for
+        # now.
+        _draw_main()
+        _draw_input_dialog(win, title, info_lines, s, i, hscroll)
+        curses.doupdate()
+
+
+        c = _getch_compat(win)
+
+        if c == curses.KEY_RESIZE:
+            # Resize the main display too. The dialog floats above it.
+            _resize_main()
+            _resize_input_dialog(win, title, info_lines)
+
+        elif c == "\n":
+            _safe_curs_set(0)
+            return s
+
+        elif c == "\x1B":  # \x1B = ESC
+            _safe_curs_set(0)
+            return None
+
+        else:
+            s, i, hscroll = _edit_text(c, s, i, hscroll, edit_width())
+
+
+def _resize_input_dialog(win, title, info_lines):
+    # Resizes the input dialog to a size appropriate for the terminal size
+
+    screen_height, screen_width = _stdscr.getmaxyx()
+
+    win_height = 5
+    if info_lines:
+        win_height += len(info_lines) + 1
+    win_height = min(win_height, screen_height)
+
+    win_width = max(_INPUT_DIALOG_MIN_WIDTH,
+                    len(title) + 4,
+                    *(len(line) + 4 for line in info_lines))
+    win_width = min(win_width, screen_width)
+
+    win.resize(win_height, win_width)
+    win.mvwin((screen_height - win_height)//2,
+              (screen_width - win_width)//2)
+
+
+def _draw_input_dialog(win, title, info_lines, s, i, hscroll):
+    edit_width = _width(win) - 4
+
+    win.erase()
+
+    # Note: Perhaps having a separate window for the input field would be nicer
+    visible_s = s[hscroll:hscroll + edit_width]
+    _safe_addstr(win, 2, 2, visible_s + " "*(edit_width - len(visible_s)),
+                 _style["edit"])
+
+    for linenr, line in enumerate(info_lines):
+        _safe_addstr(win, 4 + linenr, 2, line)
+
+    # Draw the frame last so that it overwrites the body text for small windows
+    _draw_frame(win, title)
+
+    _safe_move(win, 2, 2 + i - hscroll)
+
+    win.noutrefresh()
+
+
+def _load_dialog():
+    # Dialog for loading a new configuration
+
+    global _conf_changed
+    global _conf_filename
+    global _show_all
+
+    if _conf_changed:
+        c = _key_dialog(
+            "Load",
+            "You have unsaved changes. Load new\n"
+            "configuration anyway?\n"
+            "\n"
+            "         (O)K  (C)ancel",
+            "oc")
+
+        if c is None or c == "c":
+            return
+
+    filename = _conf_filename
+    while True:
+        filename = _input_dialog("File to load", filename, _load_save_info())
+        if filename is None:
+            return
+
+        filename = os.path.expanduser(filename)
+
+        if _try_load(filename):
+            _conf_filename = filename
+            _conf_changed = _needs_save()
+
+            # Turn on show-all mode if the selected node is not visible after
+            # loading the new configuration. _shown still holds the old state.
+            if _shown[_sel_node_i] not in _shown_nodes(_cur_menu):
+                _show_all = True
+
+            _update_menu()
+
+            # The message dialog indirectly updates the menu display, so _msg()
+            # must be called after the new state has been initialized
+            _msg("Success", "Loaded " + filename)
+            return
+
+
+def _try_load(filename):
+    # Tries to load a configuration file. Pops up an error and returns False on
+    # failure.
+    #
+    # filename:
+    #   Configuration file to load
+
+    try:
+        _kconf.load_config(filename)
+        return True
+    except EnvironmentError as e:
+        _error("Error loading '{}'\n\n{} (errno: {})"
+               .format(filename, e.strerror, errno.errorcode[e.errno]))
+        return False
+
+
+def _save_dialog(save_fn, default_filename, description):
+    # Dialog for saving the current configuration
+    #
+    # save_fn:
+    #   Function to call with 'filename' to save the file
+    #
+    # default_filename:
+    #   Prefilled filename in the input field
+    #
+    # description:
+    #   String describing the thing being saved
+    #
+    # Return value:
+    #   The path to the saved file, or None if no file was saved
+
+    filename = default_filename
+    while True:
+        filename = _input_dialog("Filename to save {} to".format(description),
+                                 filename, _load_save_info())
+        if filename is None:
+            return None
+
+        filename = os.path.expanduser(filename)
+
+        msg = _try_save(save_fn, filename, description)
+        if msg:
+            _msg("Success", msg)
+            return filename
+
+
+def _try_save(save_fn, filename, description):
+    # Tries to save a configuration file. Returns a message to print on
+    # success.
+    #
+    # save_fn:
+    #   Function to call with 'filename' to save the file
+    #
+    # description:
+    #   String describing the thing being saved
+    #
+    # Return value:
+    #   A message to print on success, and None on failure
+
+    try:
+        # save_fn() returns a message to print
+        return save_fn(filename)
+    except EnvironmentError as e:
+        _error("Error saving {} to '{}'\n\n{} (errno: {})"
+               .format(description, e.filename, e.strerror,
+                       errno.errorcode[e.errno]))
+        return None
+
+
+def _key_dialog(title, text, keys):
+    # Pops up a dialog that can be closed by pressing a key
+    #
+    # title:
+    #   Title to display at the top of the dialog window's border
+    #
+    # text:
+    #   Text to show in the dialog
+    #
+    # keys:
+    #   List of keys that will close the dialog. Other keys (besides ESC) are
+    #   ignored. The caller is responsible for providing a hint about which
+    #   keys can be pressed in 'text'.
+    #
+    # Return value:
+    #   The key that was pressed to close the dialog. Uppercase characters are
+    #   converted to lowercase. ESC will always close the dialog, and returns
+    #   None.
+
+    win = _styled_win("body")
+    win.keypad(True)
+
+    _resize_key_dialog(win, text)
+
+    while True:
+        # See _input_dialog()
+        _draw_main()
+        _draw_key_dialog(win, title, text)
+        curses.doupdate()
+
+
+        c = _getch_compat(win)
+
+        if c == curses.KEY_RESIZE:
+            # Resize the main display too. The dialog floats above it.
+            _resize_main()
+            _resize_key_dialog(win, text)
+
+        elif c == "\x1B":  # \x1B = ESC
+            return None
+
+        elif isinstance(c, str):
+            c = c.lower()
+            if c in keys:
+                return c
+
+
+def _resize_key_dialog(win, text):
+    # Resizes the key dialog to a size appropriate for the terminal size
+
+    screen_height, screen_width = _stdscr.getmaxyx()
+
+    lines = text.split("\n")
+
+    win_height = min(len(lines) + 4, screen_height)
+    win_width = min(max(len(line) for line in lines) + 4, screen_width)
+
+    win.resize(win_height, win_width)
+    win.mvwin((screen_height - win_height)//2,
+              (screen_width - win_width)//2)
+
+
+def _draw_key_dialog(win, title, text):
+    win.erase()
+
+    for i, line in enumerate(text.split("\n")):
+        _safe_addstr(win, 2 + i, 2, line)
+
+    # Draw the frame last so that it overwrites the body text for small windows
+    _draw_frame(win, title)
+
+    win.noutrefresh()
+
+
+def _draw_frame(win, title):
+    # Draw a frame around the inner edges of 'win', with 'title' at the top
+
+    win_height, win_width = win.getmaxyx()
+
+    win.attron(_style["frame"])
+
+    # Draw top/bottom edge
+    _safe_hline(win,              0, 0, " ", win_width)
+    _safe_hline(win, win_height - 1, 0, " ", win_width)
+
+    # Draw left/right edge
+    _safe_vline(win, 0,             0, " ", win_height)
+    _safe_vline(win, 0, win_width - 1, " ", win_height)
+
+    # Draw title
+    _safe_addstr(win, 0, max((win_width - len(title))//2, 0), title)
+
+    win.attroff(_style["frame"])
+
+
+def _jump_to_dialog():
+    # Implements the jump-to dialog, where symbols can be looked up via
+    # incremental search and jumped to.
+    #
+    # Returns True if the user jumped to a symbol, and False if the dialog was
+    # canceled.
+
+    s = ""  # Search text
+    prev_s = None  # Previous search text
+    s_i = 0  # Search text cursor position
+    hscroll = 0  # Horizontal scroll offset
+
+    sel_node_i = 0  # Index of selected row
+    scroll = 0  # Index in 'matches' of the top row of the list
+
+    # Edit box at the top
+    edit_box = _styled_win("jump-edit")
+    edit_box.keypad(True)
+
+    # List of matches
+    matches_win = _styled_win("list")
+
+    # Bottom separator, with arrows pointing down
+    bot_sep_win = _styled_win("separator")
+
+    # Help window with instructions at the bottom
+    help_win = _styled_win("help")
+
+    # Give windows their initial size
+    _resize_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
+                           sel_node_i, scroll)
+
+    _safe_curs_set(2)
+
+    # Logic duplication with _select_{next,prev}_menu_entry(), except we do a
+    # functional variant that returns the new (sel_node_i, scroll) values to
+    # avoid 'nonlocal'. TODO: Can this be factored out in some nice way?
+
+    def select_next_match():
+        if sel_node_i == len(matches) - 1:
+            return sel_node_i, scroll
+
+        if sel_node_i + 1 >= scroll + _height(matches_win) - _SCROLL_OFFSET \
+           and scroll < _max_scroll(matches, matches_win):
+
+            return sel_node_i + 1, scroll + 1
+
+        return sel_node_i + 1, scroll
+
+    def select_prev_match():
+        if sel_node_i == 0:
+            return sel_node_i, scroll
+
+        if sel_node_i - 1 < scroll + _SCROLL_OFFSET:
+            return sel_node_i - 1, max(scroll - 1, 0)
+
+        return sel_node_i - 1, scroll
+
+    while True:
+        if s != prev_s:
+            # The search text changed. Find new matching nodes.
+
+            prev_s = s
+
+            try:
+                # We could use re.IGNORECASE here instead of lower(), but this
+                # is noticeably less jerky while inputting regexes like
+                # '.*debug$' (though the '.*' is redundant there). Those
+                # probably have bad interactions with re.search(), which
+                # matches anywhere in the string.
+                #
+                # It's not horrible either way. Just a bit smoother.
+                regex_searches = [re.compile(regex).search
+                                  for regex in s.lower().split()]
+
+                # No exception thrown, so the regexes are okay
+                bad_re = None
+
+                # List of matching nodes
+                matches = []
+                add_match = matches.append
+
+                # Search symbols and choices
+
+                for node in _sorted_sc_nodes():
+                    # Symbol/choice
+                    sc = node.item
+
+                    for search in regex_searches:
+                        # Both the name and the prompt might be missing, since
+                        # we're searching both symbols and choices
+
+                        # Does the regex match either the symbol name or the
+                        # prompt (if any)?
+                        if not (sc.name and search(sc.name.lower()) or
+                                node.prompt and search(node.prompt[0].lower())):
+
+                            # Give up on the first regex that doesn't match, to
+                            # speed things up a bit when multiple regexes are
+                            # entered
+                            break
+
+                    else:
+                        add_match(node)
+
+                # Search menus and comments
+
+                for node in _sorted_menu_comment_nodes():
+                    for search in regex_searches:
+                        if not search(node.prompt[0].lower()):
+                            break
+                    else:
+                        add_match(node)
+
+            except re.error as e:
+                # Bad regex. Remember the error message so we can show it.
+                bad_re = "Bad regular expression"
+                # re.error.msg was added in Python 3.5
+                if hasattr(e, "msg"):
+                    bad_re += ": " + e.msg
+
+                matches = []
+
+            # Reset scroll and jump to the top of the list of matches
+            sel_node_i = scroll = 0
+
+        _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
+                             s, s_i, hscroll,
+                             bad_re, matches, sel_node_i, scroll)
+        curses.doupdate()
+
+
+        c = _getch_compat(edit_box)
+
+        if c == "\n":
+            if matches:
+                _jump_to(matches[sel_node_i])
+                _safe_curs_set(0)
+                return True
+
+        elif c == "\x1B":  # \x1B = ESC
+            _safe_curs_set(0)
+            return False
+
+        elif c == curses.KEY_RESIZE:
+            # We adjust the scroll so that the selected node stays visible in
+            # the list when the terminal is resized, hence the 'scroll'
+            # assignment
+            scroll = _resize_jump_to_dialog(
+                edit_box, matches_win, bot_sep_win, help_win,
+                sel_node_i, scroll)
+
+        elif c == "\x06":  # \x06 = Ctrl-F
+            if matches:
+                _safe_curs_set(0)
+                _info_dialog(matches[sel_node_i], True)
+                _safe_curs_set(2)
+
+                scroll = _resize_jump_to_dialog(
+                    edit_box, matches_win, bot_sep_win, help_win,
+                    sel_node_i, scroll)
+
+        elif c == curses.KEY_DOWN:
+            sel_node_i, scroll = select_next_match()
+
+        elif c == curses.KEY_UP:
+            sel_node_i, scroll = select_prev_match()
+
+        elif c in (curses.KEY_NPAGE, "\x04"):  # Page Down/Ctrl-D
+            # Keep it simple. This way we get sane behavior for small windows,
+            # etc., for free.
+            for _ in range(_PG_JUMP):
+                sel_node_i, scroll = select_next_match()
+
+        # Page Up (no Ctrl-U, as it's already used by the edit box)
+        elif c == curses.KEY_PPAGE:
+            for _ in range(_PG_JUMP):
+                sel_node_i, scroll = select_prev_match()
+
+        elif c == curses.KEY_END:
+            sel_node_i = len(matches) - 1
+            scroll = _max_scroll(matches, matches_win)
+
+        elif c == curses.KEY_HOME:
+            sel_node_i = scroll = 0
+
+        else:
+            s, s_i, hscroll = _edit_text(c, s, s_i, hscroll,
+                                         _width(edit_box) - 2)
+
+
+# Obscure Python: We never pass a value for cached_nodes, and it keeps pointing
+# to the same list. This avoids a global.
+def _sorted_sc_nodes(cached_nodes=[]):
+    # Returns a sorted list of symbol and choice nodes to search. The symbol
+    # nodes appear first, sorted by name, and then the choice nodes, sorted by
+    # prompt and (secondarily) name.
+
+    if not cached_nodes:
+        # Add symbol nodes
+        for sym in sorted(_kconf.unique_defined_syms,
+                          key=lambda sym: sym.name):
+            # += is in-place for lists
+            cached_nodes += sym.nodes
+
+        # Add choice nodes
+
+        choices = sorted(_kconf.unique_choices,
+                         key=lambda choice: choice.name or "")
+
+        cached_nodes += sorted(
+            [node for choice in choices for node in choice.nodes],
+            key=lambda node: node.prompt[0] if node.prompt else "")
+
+    return cached_nodes
+
+
+def _sorted_menu_comment_nodes(cached_nodes=[]):
+    # Returns a list of menu and comment nodes to search, sorted by prompt,
+    # with the menus first
+
+    if not cached_nodes:
+        def prompt_text(mc):
+            return mc.prompt[0]
+
+        cached_nodes += sorted(_kconf.menus, key=prompt_text)
+        cached_nodes += sorted(_kconf.comments, key=prompt_text)
+
+    return cached_nodes
+
+
+def _resize_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
+                           sel_node_i, scroll):
+    # Resizes the jump-to dialog to fill the terminal.
+    #
+    # Returns the new scroll index. We adjust the scroll if needed so that the
+    # selected node stays visible.
+
+    screen_height, screen_width = _stdscr.getmaxyx()
+
+    bot_sep_win.resize(1, screen_width)
+
+    help_win_height = len(_JUMP_TO_HELP_LINES)
+    matches_win_height = screen_height - help_win_height - 4
+
+    if matches_win_height >= 1:
+        edit_box.resize(3, screen_width)
+        matches_win.resize(matches_win_height, screen_width)
+        help_win.resize(help_win_height, screen_width)
+
+        matches_win.mvwin(3, 0)
+        bot_sep_win.mvwin(3 + matches_win_height, 0)
+        help_win.mvwin(3 + matches_win_height + 1, 0)
+    else:
+        # Degenerate case. Give up on nice rendering and just prevent errors.
+
+        matches_win_height = 1
+
+        edit_box.resize(screen_height, screen_width)
+        matches_win.resize(1, screen_width)
+        help_win.resize(1, screen_width)
+
+        for win in matches_win, bot_sep_win, help_win:
+            win.mvwin(0, 0)
+
+    # Adjust the scroll so that the selected row is still within the window, if
+    # needed
+    if sel_node_i - scroll >= matches_win_height:
+        return sel_node_i - matches_win_height + 1
+    return scroll
+
+
+def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
+                         s, s_i, hscroll,
+                         bad_re, matches, sel_node_i, scroll):
+
+    edit_width = _width(edit_box) - 2
+
+    #
+    # Update list of matches
+    #
+
+    matches_win.erase()
+
+    if matches:
+        for i in range(scroll,
+                       min(scroll + _height(matches_win), len(matches))):
+
+            node = matches[i]
+
+            if isinstance(node.item, (Symbol, Choice)):
+                node_str = _name_and_val_str(node.item)
+                if node.prompt:
+                    node_str += ' "{}"'.format(node.prompt[0])
+            elif node.item == MENU:
+                node_str = 'menu "{}"'.format(node.prompt[0])
+            else:  # node.item == COMMENT
+                node_str = 'comment "{}"'.format(node.prompt[0])
+
+            _safe_addstr(matches_win, i - scroll, 0, node_str,
+                         _style["selection" if i == sel_node_i else "list"])
+
+    else:
+        # bad_re holds the error message from the re.error exception on errors
+        _safe_addstr(matches_win, 0, 0, bad_re or "No matches")
+
+    matches_win.noutrefresh()
+
+    #
+    # Update bottom separator line
+    #
+
+    bot_sep_win.erase()
+
+    # Draw arrows pointing down if the symbol list is scrolled up
+    if scroll < _max_scroll(matches, matches_win):
+        _safe_hline(bot_sep_win, 0, 4, curses.ACS_DARROW, _N_SCROLL_ARROWS)
+
+    bot_sep_win.noutrefresh()
+
+    #
+    # Update help window at bottom
+    #
+
+    help_win.erase()
+
+    for i, line in enumerate(_JUMP_TO_HELP_LINES):
+        _safe_addstr(help_win, i, 0, line)
+
+    help_win.noutrefresh()
+
+    #
+    # Update edit box. We do this last since it makes it handy to position the
+    # cursor.
+    #
+
+    edit_box.erase()
+
+    _draw_frame(edit_box, "Jump to symbol/choice/menu/comment")
+
+    # Draw arrows pointing up if the symbol list is scrolled down
+    if scroll > 0:
+        # TODO: Bit ugly that _style["frame"] is repeated here
+        _safe_hline(edit_box, 2, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS,
+                    _style["frame"])
+
+    visible_s = s[hscroll:hscroll + edit_width]
+    _safe_addstr(edit_box, 1, 1, visible_s)
+
+    _safe_move(edit_box, 1, 1 + s_i - hscroll)
+
+    edit_box.noutrefresh()
+
+
+def _info_dialog(node, from_jump_to_dialog):
+    # Shows a fullscreen window with information about 'node'.
+    #
+    # If 'from_jump_to_dialog' is True, the information dialog was opened from
+    # within the jump-to-dialog. In this case, we make '/' from within the
+    # information dialog just return, to avoid a confusing recursive invocation
+    # of the jump-to-dialog.
+
+    # Top row, with title and arrows point up
+    top_line_win = _styled_win("separator")
+
+    # Text display
+    text_win = _styled_win("text")
+    text_win.keypad(True)
+
+    # Bottom separator, with arrows pointing down
+    bot_sep_win = _styled_win("separator")
+
+    # Help window with keys at the bottom
+    help_win = _styled_win("help")
+
+    # Give windows their initial size
+    _resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win)
+
+
+    # Get lines of help text
+    lines = _info_str(node).split("\n")
+
+    # Index of first row in 'lines' to show
+    scroll = 0
+
+    while True:
+        _draw_info_dialog(node, lines, scroll, top_line_win, text_win,
+                          bot_sep_win, help_win)
+        curses.doupdate()
+
+
+        c = _getch_compat(text_win)
+
+        if c == curses.KEY_RESIZE:
+            _resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win)
+
+        elif c in (curses.KEY_DOWN, "j", "J"):
+            if scroll < _max_scroll(lines, text_win):
+                scroll += 1
+
+        elif c in (curses.KEY_NPAGE, "\x04"):  # Page Down/Ctrl-D
+            scroll = min(scroll + _PG_JUMP, _max_scroll(lines, text_win))
+
+        elif c in (curses.KEY_PPAGE, "\x15"):  # Page Up/Ctrl-U
+            scroll = max(scroll - _PG_JUMP, 0)
+
+        elif c in (curses.KEY_END, "G"):
+            scroll = _max_scroll(lines, text_win)
+
+        elif c in (curses.KEY_HOME, "g"):
+            scroll = 0
+
+        elif c in (curses.KEY_UP, "k", "K"):
+            if scroll > 0:
+                scroll -= 1
+
+        elif c == "/":
+            # Support starting a search from within the information dialog
+
+            if from_jump_to_dialog:
+                return  # Avoid recursion
+
+            if _jump_to_dialog():
+                return  # Jumped to a symbol. Cancel the information dialog.
+
+            # Stay in the information dialog if the jump-to dialog was
+            # canceled. Resize it in case the terminal was resized while the
+            # fullscreen jump-to dialog was open.
+            _resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win)
+
+        elif c in (curses.KEY_LEFT, curses.KEY_BACKSPACE, _ERASE_CHAR,
+                   "\x1B",  # \x1B = ESC
+                   "q", "Q", "h", "H"):
+
+            return
+
+
+def _resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win):
+    # Resizes the info dialog to fill the terminal
+
+    screen_height, screen_width = _stdscr.getmaxyx()
+
+    top_line_win.resize(1, screen_width)
+    bot_sep_win.resize(1, screen_width)
+
+    help_win_height = len(_INFO_HELP_LINES)
+    text_win_height = screen_height - help_win_height - 2
+
+    if text_win_height >= 1:
+        text_win.resize(text_win_height, screen_width)
+        help_win.resize(help_win_height, screen_width)
+
+        text_win.mvwin(1, 0)
+        bot_sep_win.mvwin(1 + text_win_height, 0)
+        help_win.mvwin(1 + text_win_height + 1, 0)
+    else:
+        # Degenerate case. Give up on nice rendering and just prevent errors.
+
+        text_win.resize(1, screen_width)
+        help_win.resize(1, screen_width)
+
+        for win in text_win, bot_sep_win, help_win:
+            win.mvwin(0, 0)
+
+
+def _draw_info_dialog(node, lines, scroll, top_line_win, text_win,
+                      bot_sep_win, help_win):
+
+    text_win_height, text_win_width = text_win.getmaxyx()
+
+    # Note: The top row is deliberately updated last. See _draw_main().
+
+    #
+    # Update text display
+    #
+
+    text_win.erase()
+
+    for i, line in enumerate(lines[scroll:scroll + text_win_height]):
+        _safe_addstr(text_win, i, 0, line)
+
+    text_win.noutrefresh()
+
+    #
+    # Update bottom separator line
+    #
+
+    bot_sep_win.erase()
+
+    # Draw arrows pointing down if the symbol window is scrolled up
+    if scroll < _max_scroll(lines, text_win):
+        _safe_hline(bot_sep_win, 0, 4, curses.ACS_DARROW, _N_SCROLL_ARROWS)
+
+    bot_sep_win.noutrefresh()
+
+    #
+    # Update help window at bottom
+    #
+
+    help_win.erase()
+
+    for i, line in enumerate(_INFO_HELP_LINES):
+        _safe_addstr(help_win, i, 0, line)
+
+    help_win.noutrefresh()
+
+    #
+    # Update top row
+    #
+
+    top_line_win.erase()
+
+    # Draw arrows pointing up if the information window is scrolled down. Draw
+    # them before drawing the title, so the title ends up on top for small
+    # windows.
+    if scroll > 0:
+        _safe_hline(top_line_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
+
+    title = ("Symbol" if isinstance(node.item, Symbol) else
+             "Choice" if isinstance(node.item, Choice) else
+             "Menu"   if node.item == MENU else
+             "Comment") + " information"
+    _safe_addstr(top_line_win, 0, max((text_win_width - len(title))//2, 0),
+                 title)
+
+    top_line_win.noutrefresh()
+
+
+def _info_str(node):
+    # Returns information about the menu node 'node' as a string.
+    #
+    # The helper functions are responsible for adding newlines. This allows
+    # them to return "" if they don't want to add any output.
+
+    if isinstance(node.item, Symbol):
+        sym = node.item
+
+        return (
+            _name_info(sym) +
+            _prompt_info(sym) +
+            "Type: {}\n".format(TYPE_TO_STR[sym.type]) +
+            _value_info(sym) +
+            _help_info(sym) +
+            _direct_dep_info(sym) +
+            _defaults_info(sym) +
+            _select_imply_info(sym) +
+            _kconfig_def_info(sym)
+        )
+
+    if isinstance(node.item, Choice):
+        choice = node.item
+
+        return (
+            _name_info(choice) +
+            _prompt_info(choice) +
+            "Type: {}\n".format(TYPE_TO_STR[choice.type]) +
+            'Mode: {}\n'.format(choice.str_value) +
+            _help_info(choice) +
+            _choice_syms_info(choice) +
+            _direct_dep_info(choice) +
+            _defaults_info(choice) +
+            _kconfig_def_info(choice)
+        )
+
+    return _kconfig_def_info(node)  # node.item in (MENU, COMMENT)
+
+
+def _name_info(sc):
+    # Returns a string with the name of the symbol/choice. Names are optional
+    # for choices.
+
+    return "Name: {}\n".format(sc.name) if sc.name else ""
+
+
+def _prompt_info(sc):
+    # Returns a string listing the prompts of 'sc' (Symbol or Choice)
+
+    s = ""
+
+    for node in sc.nodes:
+        if node.prompt:
+            s += "Prompt: {}\n".format(node.prompt[0])
+
+    return s
+
+
+def _value_info(sym):
+    # Returns a string showing 'sym's value
+
+    # Only put quotes around the value for string symbols
+    return "Value: {}\n".format(
+        '"{}"'.format(sym.str_value)
+        if sym.orig_type == STRING
+        else sym.str_value)
+
+
+def _choice_syms_info(choice):
+    # Returns a string listing the choice symbols in 'choice'. Adds
+    # "(selected)" next to the selected one.
+
+    s = "Choice symbols:\n"
+
+    for sym in choice.syms:
+        s += "  - " + sym.name
+        if sym is choice.selection:
+            s += " (selected)"
+        s += "\n"
+
+    return s + "\n"
+
+
+def _help_info(sc):
+    # Returns a string with the help text(s) of 'sc' (Symbol or Choice).
+    # Symbols and choices defined in multiple locations can have multiple help
+    # texts.
+
+    s = "\n"
+
+    for node in sc.nodes:
+        if node.help is not None:
+            s += "Help:\n\n{}\n\n".format(_indent(node.help, 2))
+
+    return s
+
+
+def _direct_dep_info(sc):
+    # Returns a string describing the direct dependencies of 'sc' (Symbol or
+    # Choice). The direct dependencies are the OR of the dependencies from each
+    # definition location. The dependencies at each definition location come
+    # from 'depends on' and dependencies inherited from parent items.
+
+    return "" if sc.direct_dep is _kconf.y else \
+        'Direct dependencies (={}):\n{}\n' \
+        .format(TRI_TO_STR[expr_value(sc.direct_dep)],
+                _split_expr_info(sc.direct_dep, 2))
+
+
+def _defaults_info(sc):
+    # Returns a string describing the defaults of 'sc' (Symbol or Choice)
+
+    if not sc.defaults:
+        return ""
+
+    s = "Default"
+    if len(sc.defaults) > 1:
+        s += "s"
+    s += ":\n"
+
+    for val, cond in sc.orig_defaults:
+        s += "  - "
+        if isinstance(sc, Symbol):
+            s += _expr_str(val)
+
+            # Skip the tristate value hint if the expression is just a single
+            # symbol. _expr_str() already shows its value as a string.
+            #
+            # This also avoids showing the tristate value for string/int/hex
+            # defaults, which wouldn't make any sense.
+            if isinstance(val, tuple):
+                s += '  (={})'.format(TRI_TO_STR[expr_value(val)])
+        else:
+            # Don't print the value next to the symbol name for choice
+            # defaults, as it looks a bit confusing
+            s += val.name
+        s += "\n"
+
+        if cond is not _kconf.y:
+            s += "    Condition (={}):\n{}" \
+                 .format(TRI_TO_STR[expr_value(cond)],
+                         _split_expr_info(cond, 4))
+
+    return s + "\n"
+
+
+def _split_expr_info(expr, indent):
+    # Returns a string with 'expr' split into its top-level && or || operands,
+    # with one operand per line, together with the operand's value. This is
+    # usually enough to get something readable for long expressions. A fancier
+    # recursive thingy would be possible too.
+    #
+    # indent:
+    #   Number of leading spaces to add before the split expression.
+
+    if len(split_expr(expr, AND)) > 1:
+        split_op = AND
+        op_str = "&&"
+    else:
+        split_op = OR
+        op_str = "||"
+
+    s = ""
+    for i, term in enumerate(split_expr(expr, split_op)):
+        s += "{}{} {}".format(indent*" ",
+                              "  " if i == 0 else op_str,
+                              _expr_str(term))
+
+        # Don't bother showing the value hint if the expression is just a
+        # single symbol. _expr_str() already shows its value.
+        if isinstance(term, tuple):
+            s += "  (={})".format(TRI_TO_STR[expr_value(term)])
+
+        s += "\n"
+
+    return s
+
+
+def _select_imply_info(sym):
+    # Returns a string with information about which symbols 'select' or 'imply'
+    # 'sym'. The selecting/implying symbols are grouped according to which
+    # value they select/imply 'sym' to (n/m/y).
+
+    def sis(expr, val, title):
+        # sis = selects/implies
+        sis = [si for si in split_expr(expr, OR) if expr_value(si) == val]
+        if not sis:
+            return ""
+
+        res = title
+        for si in sis:
+            res += "  - {}\n".format(split_expr(si, AND)[0].name)
+        return res + "\n"
+
+    s = ""
+
+    if sym.rev_dep is not _kconf.n:
+        s += sis(sym.rev_dep, 2,
+                 "Symbols currently y-selecting this symbol:\n")
+        s += sis(sym.rev_dep, 1,
+                 "Symbols currently m-selecting this symbol:\n")
+        s += sis(sym.rev_dep, 0,
+                 "Symbols currently n-selecting this symbol (no effect):\n")
+
+    if sym.weak_rev_dep is not _kconf.n:
+        s += sis(sym.weak_rev_dep, 2,
+                 "Symbols currently y-implying this symbol:\n")
+        s += sis(sym.weak_rev_dep, 1,
+                 "Symbols currently m-implying this symbol:\n")
+        s += sis(sym.weak_rev_dep, 0,
+                 "Symbols currently n-implying this symbol (no effect):\n")
+
+    return s
+
+
+def _kconfig_def_info(item):
+    # Returns a string with the definition of 'item' in Kconfig syntax,
+    # together with the definition location(s) and their include and menu paths
+
+    nodes = [item] if isinstance(item, MenuNode) else item.nodes
+
+    s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \
+        .format("s" if len(nodes) > 1 else "")
+    s += (len(s) - 1)*"="
+
+    for node in nodes:
+        s += "\n\n" \
+             "At {}:{}\n" \
+             "{}" \
+             "Menu path: {}\n\n" \
+             "{}" \
+             .format(node.filename, node.linenr,
+                     _include_path_info(node),
+                     _menu_path_info(node),
+                     _indent(node.custom_str(_name_and_val_str), 2))
+
+    return s
+
+
+def _include_path_info(node):
+    if not node.include_path:
+        # In the top-level Kconfig file
+        return ""
+
+    return "Included via {}\n".format(
+        " -> ".join("{}:{}".format(filename, linenr)
+                    for filename, linenr in node.include_path))
+
+
+def _menu_path_info(node):
+    # Returns a string describing the menu path leading up to 'node'
+
+    path = ""
+
+    while node.parent is not _kconf.top_node:
+        node = node.parent
+
+        # Promptless choices might appear among the parents. Use
+        # standard_sc_expr_str() for them, so that they show up as
+        # '<choice (name if any)>'.
+        path = " -> " + (node.prompt[0] if node.prompt else
+                         standard_sc_expr_str(node.item)) + path
+
+    return "(Top)" + path
+
+
+def _indent(s, n):
+    # Returns 's' with each line indented 'n' spaces. textwrap.indent() is not
+    # available in Python 2 (it's 3.3+).
+
+    return "\n".join(n*" " + line for line in s.split("\n"))
+
+
+def _name_and_val_str(sc):
+    # Custom symbol/choice printer that shows symbol values after symbols
+
+    # Show the values of non-constant (non-quoted) symbols that don't look like
+    # numbers. Things like 123 are actually symbol references, and only work as
+    # expected due to undefined symbols getting their name as their value.
+    # Showing the symbol value for those isn't helpful though.
+    if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name):
+        if not sc.nodes:
+            # Undefined symbol reference
+            return "{}(undefined/n)".format(sc.name)
+
+        return '{}(={})'.format(sc.name, sc.str_value)
+
+    # For other items, use the standard format
+    return standard_sc_expr_str(sc)
+
+
+def _expr_str(expr):
+    # Custom expression printer that shows symbol values
+    return expr_str(expr, _name_and_val_str)
+
+
+def _styled_win(style):
+    # Returns a new curses window with style 'style' and space as the fill
+    # character. The initial dimensions are (1, 1), so the window needs to be
+    # sized and positioned separately.
+
+    win = curses.newwin(1, 1)
+    _set_style(win, style)
+    return win
+
+
+def _set_style(win, style):
+    # Changes the style of an existing window
+
+    win.bkgdset(" ", _style[style])
+
+
+def _max_scroll(lst, win):
+    # Assuming 'lst' is a list of items to be displayed in 'win',
+    # returns the maximum number of steps 'win' can be scrolled down.
+    # We stop scrolling when the bottom item is visible.
+
+    return max(0, len(lst) - _height(win))
+
+
+def _edit_text(c, s, i, hscroll, width):
+    # Implements text editing commands for edit boxes. Takes a character (which
+    # could also be e.g. curses.KEY_LEFT) and the edit box state, and returns
+    # the new state after the character has been processed.
+    #
+    # c:
+    #   Character from user
+    #
+    # s:
+    #   Current contents of string
+    #
+    # i:
+    #   Current cursor index in string
+    #
+    # hscroll:
+    #   Index in s of the leftmost character in the edit box, for horizontal
+    #   scrolling
+    #
+    # width:
+    #   Width in characters of the edit box
+    #
+    # Return value:
+    #   An (s, i, hscroll) tuple for the new state
+
+    if c == curses.KEY_LEFT:
+        if i > 0:
+            i -= 1
+
+    elif c == curses.KEY_RIGHT:
+        if i < len(s):
+            i += 1
+
+    elif c in (curses.KEY_HOME, "\x01"):  # \x01 = CTRL-A
+        i = 0
+
+    elif c in (curses.KEY_END, "\x05"):  # \x05 = CTRL-E
+        i = len(s)
+
+    elif c in (curses.KEY_BACKSPACE, _ERASE_CHAR):
+        if i > 0:
+            s = s[:i-1] + s[i:]
+            i -= 1
+
+    elif c == curses.KEY_DC:
+        s = s[:i] + s[i+1:]
+
+    elif c == "\x17":  # \x17 = CTRL-W
+        # The \W removes characters like ',' one at a time
+        new_i = re.search(r"(?:\w*|\W)\s*$", s[:i]).start()
+        s = s[:new_i] + s[i:]
+        i = new_i
+
+    elif c == "\x0B":  # \x0B = CTRL-K
+        s = s[:i]
+
+    elif c == "\x15":  # \x15 = CTRL-U
+        s = s[i:]
+        i = 0
+
+    elif isinstance(c, str):
+        # Insert character
+        s = s[:i] + c + s[i:]
+        i += 1
+
+    # Adjust the horizontal scroll so that the cursor never touches the left or
+    # right edges of the edit box, except when it's at the beginning or the end
+    # of the string
+    if i < hscroll + _SCROLL_OFFSET:
+        hscroll = max(i - _SCROLL_OFFSET, 0)
+    elif i >= hscroll + width - _SCROLL_OFFSET:
+        max_scroll = max(len(s) - width + 1, 0)
+        hscroll = min(i - width + _SCROLL_OFFSET + 1, max_scroll)
+
+    return s, i, hscroll
+
+
+def _load_save_info():
+    # Returns an information string for load/save dialog boxes
+
+    return "(Relative to {})\n\nRefer to your home directory with ~" \
+           .format(os.path.join(os.getcwd(), ""))
+
+
+def _msg(title, text):
+    # Pops up a message dialog that can be dismissed with Space/Enter/ESC
+
+    _key_dialog(title, text, " \n")
+
+
+def _error(text):
+    # Pops up an error dialog that can be dismissed with Space/Enter/ESC
+
+    _msg("Error", text)
+
+
+def _node_str(node):
+    # Returns the complete menu entry text for a menu node.
+    #
+    # Example return value: "[*] Support for X"
+
+    # Calculate the indent to print the item with by checking how many levels
+    # above it the closest 'menuconfig' item is (this includes menus and
+    # choices as well as menuconfig symbols)
+    indent = 0
+    parent = node.parent
+    while not parent.is_menuconfig:
+        indent += _SUBMENU_INDENT
+        parent = parent.parent
+
+    # This approach gives nice alignment for empty string symbols ("()  Foo")
+    s = "{:{}}".format(_value_str(node), 3 + indent)
+
+    if _should_show_name(node):
+        if isinstance(node.item, Symbol):
+            s += " <{}>".format(node.item.name)
+        else:
+            # For choices, use standard_sc_expr_str(). That way they show up as
+            # '<choice (name if any)>'.
+            s += " " + standard_sc_expr_str(node.item)
+
+    if node.prompt:
+        if node.item == COMMENT:
+            s += " *** {} ***".format(node.prompt[0])
+        else:
+            s += " " + node.prompt[0]
+
+        if isinstance(node.item, Symbol):
+            sym = node.item
+
+            # Print "(NEW)" next to symbols without a user value (from e.g. a
+            # .config), but skip it for choice symbols in choices in y mode,
+            # and for symbols of UNKNOWN type (which generate a warning though)
+            if sym.user_value is None and sym.orig_type and \
+               not (sym.choice and sym.choice.tri_value == 2):
+
+                s += " (NEW)"
+
+    if isinstance(node.item, Choice) and node.item.tri_value == 2:
+        # Print the prompt of the selected symbol after the choice for
+        # choices in y mode
+        sym = node.item.selection
+        if sym:
+            for sym_node in sym.nodes:
+                # Use the prompt used at this choice location, in case the
+                # choice symbol is defined in multiple locations
+                if sym_node.parent is node and sym_node.prompt:
+                    s += " ({})".format(sym_node.prompt[0])
+                    break
+            else:
+                # If the symbol isn't defined at this choice location, then
+                # just use whatever prompt we can find for it
+                for sym_node in sym.nodes:
+                    if sym_node.prompt:
+                        s += " ({})".format(sym_node.prompt[0])
+                        break
+
+    # Print "--->" next to nodes that have menus that can potentially be
+    # entered. Print "----" if the menu is empty. We don't allow those to be
+    # entered.
+    if node.is_menuconfig:
+        s += "  --->" if _shown_nodes(node) else "  ----"
+
+    return s
+
+
+def _should_show_name(node):
+    # Returns True if 'node' is a symbol or choice whose name should shown (if
+    # any, as names are optional for choices)
+
+    # The 'not node.prompt' case only hits in show-all mode, for promptless
+    # symbols and choices
+    return not node.prompt or \
+           (_show_name and isinstance(node.item, (Symbol, Choice)))
+
+
+def _value_str(node):
+    # Returns the value part ("[*]", "<M>", "(foo)" etc.) of a menu node
+
+    item = node.item
+
+    if item in (MENU, COMMENT):
+        return ""
+
+    # Wouldn't normally happen, and generates a warning
+    if not item.orig_type:
+        return ""
+
+    if item.orig_type in (STRING, INT, HEX):
+        return "({})".format(item.str_value)
+
+    # BOOL or TRISTATE
+
+    if _is_y_mode_choice_sym(item):
+        return "(X)" if item.choice.selection is item else "( )"
+
+    tri_val_str = (" ", "M", "*")[item.tri_value]
+
+    if len(item.assignable) <= 1:
+        # Pinned to a single value
+        return "" if isinstance(item, Choice) else "-{}-".format(tri_val_str)
+
+    if item.type == BOOL:
+        return "[{}]".format(tri_val_str)
+
+    # item.type == TRISTATE
+    if item.assignable == (1, 2):
+        return "{{{}}}".format(tri_val_str)  # {M}/{*}
+    return "<{}>".format(tri_val_str)
+
+
+def _is_y_mode_choice_sym(item):
+    # The choice mode is an upper bound on the visibility of choice symbols, so
+    # we can check the choice symbols' own visibility to see if the choice is
+    # in y mode
+    return isinstance(item, Symbol) and item.choice and item.visibility == 2
+
+
+def _check_valid(sym, s):
+    # Returns True if the string 's' is a well-formed value for 'sym'.
+    # Otherwise, displays an error and returns False.
+
+    if sym.orig_type not in (INT, HEX):
+        return True  # Anything goes for non-int/hex symbols
+
+    base = 10 if sym.orig_type == INT else 16
+    try:
+        int(s, base)
+    except ValueError:
+        _error("'{}' is a malformed {} value"
+               .format(s, TYPE_TO_STR[sym.orig_type]))
+        return False
+
+    for low_sym, high_sym, cond in sym.ranges:
+        if expr_value(cond):
+            low_s = low_sym.str_value
+            high_s = high_sym.str_value
+
+            if not int(low_s, base) <= int(s, base) <= int(high_s, base):
+                _error("{} is outside the range {}-{}"
+                       .format(s, low_s, high_s))
+                return False
+
+            break
+
+    return True
+
+
+def _range_info(sym):
+    # Returns a string with information about the valid range for the symbol
+    # 'sym', or None if 'sym' doesn't have a range
+
+    if sym.orig_type in (INT, HEX):
+        for low, high, cond in sym.ranges:
+            if expr_value(cond):
+                return "Range: {}-{}".format(low.str_value, high.str_value)
+
+    return None
+
+
+def _is_num(name):
+    # Heuristic to see if a symbol name looks like a number, for nicer output
+    # when printing expressions. Things like 16 are actually symbol names, only
+    # they get their name as their value when the symbol is undefined.
+
+    try:
+        int(name)
+    except ValueError:
+        if not name.startswith(("0x", "0X")):
+            return False
+
+        try:
+            int(name, 16)
+        except ValueError:
+            return False
+
+    return True
+
+
+def _getch_compat(win):
+    # Uses get_wch() if available (Python 3.3+) and getch() otherwise.
+    #
+    # Also falls back on getch() if get_wch() raises curses.error, to work
+    # around an issue when resizing the terminal on at least macOS Catalina.
+    # See https://github.com/ulfalizer/Kconfiglib/issues/84.
+    #
+    # Also handles a PDCurses resizing quirk.
+
+    try:
+        c = win.get_wch()
+    except (AttributeError, curses.error):
+        c = win.getch()
+        if 0 <= c <= 255:
+            c = chr(c)
+
+    # Decent resizing behavior on PDCurses requires calling resize_term(0, 0)
+    # after receiving KEY_RESIZE, while ncurses (usually) handles terminal
+    # resizing automatically in get(_w)ch() (see the end of the
+    # resizeterm(3NCURSES) man page).
+    #
+    # resize_term(0, 0) reliably fails and does nothing on ncurses, so this
+    # hack gives ncurses/PDCurses compatibility for resizing. I don't know
+    # whether it would cause trouble for other implementations.
+    if c == curses.KEY_RESIZE:
+        try:
+            curses.resize_term(0, 0)
+        except curses.error:
+            pass
+
+    return c
+
+
+def _warn(*args):
+    # Temporarily returns from curses to shell mode and prints a warning to
+    # stderr. The warning would get lost in curses mode.
+    curses.endwin()
+    print("menuconfig warning: ", end="", file=sys.stderr)
+    print(*args, file=sys.stderr)
+    curses.doupdate()
+
+
+# Ignore exceptions from some functions that might fail, e.g. for small
+# windows. They usually do reasonable things anyway.
+
+
+def _safe_curs_set(visibility):
+    try:
+        curses.curs_set(visibility)
+    except curses.error:
+        pass
+
+
+def _safe_addstr(win, *args):
+    # Clip the line to avoid wrapping to the next line, which looks glitchy.
+    # addchstr() would do it for us, but it's not available in the 'curses'
+    # module.
+
+    attr = None
+    if isinstance(args[0], str):
+        y, x = win.getyx()
+        s = args[0]
+        if len(args) == 2:
+            attr = args[1]
+    else:
+        y, x, s = args[:3]
+        if len(args) == 4:
+            attr = args[3]
+
+    maxlen = _width(win) - x
+    s = s.expandtabs()
+
+    try:
+        # The 'curses' module uses wattr_set() internally if you pass 'attr',
+        # overwriting the background style, so setting 'attr' to 0 in the first
+        # case won't do the right thing
+        if attr is None:
+            win.addnstr(y, x, s, maxlen)
+        else:
+            win.addnstr(y, x, s, maxlen, attr)
+    except curses.error:
+        pass
+
+
+def _safe_addch(win, *args):
+    try:
+        win.addch(*args)
+    except curses.error:
+        pass
+
+
+def _safe_hline(win, *args):
+    try:
+        win.hline(*args)
+    except curses.error:
+        pass
+
+
+def _safe_vline(win, *args):
+    try:
+        win.vline(*args)
+    except curses.error:
+        pass
+
+
+def _safe_move(win, *args):
+    try:
+        win.move(*args)
+    except curses.error:
+        pass
+
+
+def _change_c_lc_ctype_to_utf8():
+    # See _CHANGE_C_LC_CTYPE_TO_UTF8
+
+    if _IS_WINDOWS:
+        # Windows rarely has issues here, and the PEP 538 implementation avoids
+        # changing the locale on it. None of the UTF-8 locales below were
+        # supported from some quick testing either. Play it safe.
+        return
+
+    def try_set_locale(loc):
+        try:
+            locale.setlocale(locale.LC_CTYPE, loc)
+            return True
+        except locale.Error:
+            return False
+
+    # Is LC_CTYPE set to the C locale?
+    if locale.setlocale(locale.LC_CTYPE) == "C":
+        # This list was taken from the PEP 538 implementation in the CPython
+        # code, in Python/pylifecycle.c
+        for loc in "C.UTF-8", "C.utf8", "UTF-8":
+            if try_set_locale(loc):
+                # LC_CTYPE successfully changed
+                return
+
+
+if __name__ == "__main__":
+    _main()
diff --git a/scripts/Kconfiglib/oldconfig.py b/scripts/Kconfiglib/oldconfig.py
new file mode 100755
index 0000000..53434b2
--- /dev/null
+++ b/scripts/Kconfiglib/oldconfig.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018-2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Implements oldconfig functionality.
+
+  1. Loads existing .config
+  2. Prompts for the value of all modifiable symbols/choices that
+     aren't already set in the .config
+  3. Writes an updated .config
+
+The default input/output filename is '.config'. A different filename can be
+passed in the KCONFIG_CONFIG environment variable.
+
+When overwriting a configuration file, the old version is saved to
+<filename>.old (e.g. .config.old).
+
+Entering '?' displays the help text of the symbol/choice, if any.
+
+Unlike 'make oldconfig', this script doesn't print menu titles and comments,
+but gives Kconfig definition locations. Printing menus and comments would be
+pretty easy to add: Look at the parents of each item, and print all menu
+prompts and comments unless they have already been printed (assuming you want
+to skip "irrelevant" menus).
+"""
+from __future__ import print_function
+
+import sys
+
+from kconfiglib import Symbol, Choice, BOOL, TRISTATE, HEX, standard_kconfig
+
+
+# Python 2/3 compatibility hack
+if sys.version_info[0] < 3:
+    input = raw_input
+
+
+def _main():
+    # Earlier symbols in Kconfig files might depend on later symbols and become
+    # visible if their values change. This flag is set to True if the value of
+    # any symbol changes, in which case we rerun the oldconfig to check for new
+    # visible symbols.
+    global conf_changed
+
+    kconf = standard_kconfig(__doc__)
+    print(kconf.load_config())
+
+    while True:
+        conf_changed = False
+
+        for node in kconf.node_iter():
+            oldconfig(node)
+
+        if not conf_changed:
+            break
+
+    print(kconf.write_config())
+
+
+def oldconfig(node):
+    """
+    Prompts the user for a value if node.item is a visible symbol/choice with
+    no user value.
+    """
+    # See main()
+    global conf_changed
+
+    # Only symbols and choices can be configured
+    if not isinstance(node.item, (Symbol, Choice)):
+        return
+
+    # Skip symbols and choices that aren't visible
+    if not node.item.visibility:
+        return
+
+    # Skip symbols and choices that don't have a prompt (at this location)
+    if not node.prompt:
+        return
+
+    if isinstance(node.item, Symbol):
+        sym = node.item
+
+        # Skip symbols that already have a user value
+        if sym.user_value is not None:
+            return
+
+        # Skip symbols that can only have a single value, due to selects
+        if len(sym.assignable) == 1:
+            return
+
+        # Skip symbols in choices in y mode. We ask once for the entire choice
+        # instead.
+        if sym.choice and sym.choice.tri_value == 2:
+            return
+
+        # Loop until the user enters a valid value or enters a blank string
+        # (for the default value)
+        while True:
+            val = input("{} ({}) [{}] ".format(
+                node.prompt[0], _name_and_loc_str(sym),
+                _default_value_str(sym)))
+
+            if val == "?":
+                _print_help(node)
+                continue
+
+            # Substitute a blank string with the default value the symbol
+            # would get
+            if not val:
+                val = sym.str_value
+
+            # Automatically add a "0x" prefix for hex symbols, like the
+            # menuconfig interface does. This isn't done when loading .config
+            # files, hence why set_value() doesn't do it automatically.
+            if sym.type == HEX and not val.startswith(("0x", "0X")):
+                val = "0x" + val
+
+            old_str_val = sym.str_value
+
+            # Kconfiglib itself will print a warning here if the value
+            # is invalid, so we don't need to bother
+            if sym.set_value(val):
+                # Valid value input. We're done with this node.
+
+                if sym.str_value != old_str_val:
+                    conf_changed = True
+
+                return
+
+    else:
+        choice = node.item
+
+        # Skip choices that already have a visible user selection...
+        if choice.user_selection and choice.user_selection.visibility == 2:
+            # ...unless there are new visible symbols in the choice. (We know
+            # they have y (2) visibility in that case, because m-visible
+            # symbols get demoted to n-visibility in y-mode choices, and the
+            # user-selected symbol had visibility y.)
+            for sym in choice.syms:
+                if sym is not choice.user_selection and sym.visibility and \
+                   sym.user_value is None:
+                    # New visible symbols in the choice
+                    break
+            else:
+                # No new visible symbols in the choice
+                return
+
+        # Get a list of available selections. The mode of the choice limits
+        # the visibility of the choice value symbols, so this will indirectly
+        # skip choices in n and m mode.
+        options = [sym for sym in choice.syms if sym.visibility == 2]
+
+        if not options:
+            # No y-visible choice value symbols
+            return
+
+        # Loop until the user enters a valid selection or a blank string (for
+        # the default selection)
+        while True:
+            print("{} ({})".format(node.prompt[0], _name_and_loc_str(choice)))
+
+            for i, sym in enumerate(options, 1):
+                print("{} {}. {} ({})".format(
+                    ">" if sym is choice.selection else " ",
+                    i,
+                    # Assume people don't define choice symbols with multiple
+                    # prompts. That generates a warning anyway.
+                    sym.nodes[0].prompt[0],
+                    sym.name))
+
+            sel_index = input("choice[1-{}]: ".format(len(options)))
+
+            if sel_index == "?":
+                _print_help(node)
+                continue
+
+            # Pick the default selection if the string is blank
+            if not sel_index:
+                choice.selection.set_value(2)
+                break
+
+            try:
+                sel_index = int(sel_index)
+            except ValueError:
+                print("Bad index", file=sys.stderr)
+                continue
+
+            if not 1 <= sel_index <= len(options):
+                print("Bad index", file=sys.stderr)
+                continue
+
+            # Valid selection
+
+            if options[sel_index - 1].tri_value != 2:
+                conf_changed = True
+
+            options[sel_index - 1].set_value(2)
+            break
+
+        # Give all of the non-selected visible choice symbols the user value n.
+        # This makes it so that the choice is no longer considered new once we
+        # do additional passes, if the reason that it was considered new was
+        # that it had new visible choice symbols.
+        #
+        # Only giving visible choice symbols the user value n means we will
+        # prompt for the choice again if later user selections make more new
+        # choice symbols visible, which is correct.
+        for sym in choice.syms:
+            if sym is not choice.user_selection and sym.visibility:
+                sym.set_value(0)
+
+
+def _name_and_loc_str(sc):
+    # Helper for printing the name of the symbol/choice 'sc' along with the
+    # location(s) in the Kconfig files where it is defined. Unnamed choices
+    # return "choice" instead of the name.
+
+    return "{}, defined at {}".format(
+        sc.name or "choice",
+        ", ".join("{}:{}".format(node.filename, node.linenr)
+                  for node in sc.nodes))
+
+
+def _print_help(node):
+    print("\n" + (node.help or "No help text\n"))
+
+
+def _default_value_str(sym):
+    # Returns the "m/M/y" string in e.g.
+    #
+    #   TRISTATE_SYM prompt (TRISTATE_SYM, defined at Kconfig:9) [n/M/y]:
+    #
+    # For string/int/hex, returns the default value as-is.
+
+    if sym.type in (BOOL, TRISTATE):
+        return "/".join(("NMY" if sym.tri_value == tri else "nmy")[tri]
+                        for tri in sym.assignable)
+
+    # string/int/hex
+    return sym.str_value
+
+
+if __name__ == "__main__":
+    _main()
diff --git a/scripts/Kconfiglib/olddefconfig.py b/scripts/Kconfiglib/olddefconfig.py
new file mode 100755
index 0000000..2dadfb4
--- /dev/null
+++ b/scripts/Kconfiglib/olddefconfig.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018-2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Updates an old .config file or creates a new one, by filling in default values
+for all new symbols. This is the same as picking the default selection for all
+symbols in oldconfig, or entering the menuconfig interface and immediately
+saving.
+
+The default input/output filename is '.config'. A different filename can be
+passed in the KCONFIG_CONFIG environment variable.
+
+When overwriting a configuration file, the old version is saved to
+<filename>.old (e.g. .config.old).
+"""
+import kconfiglib
+
+
+def main():
+    kconf = kconfiglib.standard_kconfig(__doc__)
+    print(kconf.load_config())
+    print(kconf.write_config())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/Kconfiglib/savedefconfig.py b/scripts/Kconfiglib/savedefconfig.py
new file mode 100755
index 0000000..0f36bde
--- /dev/null
+++ b/scripts/Kconfiglib/savedefconfig.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Saves a minimal configuration file that only lists symbols that differ in value
+from their defaults. Loading such a configuration file is equivalent to loading
+the "full" configuration file.
+
+Minimal configuration files are handy to start from when editing configuration
+files by hand.
+
+The default input configuration file is '.config'. A different input filename
+can be passed in the KCONFIG_CONFIG environment variable.
+
+Note: Minimal configurations can also be generated from within the menuconfig
+interface.
+"""
+import argparse
+
+import kconfiglib
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=__doc__)
+
+    parser.add_argument(
+        "--kconfig",
+        default="Kconfig",
+        help="Top-level Kconfig file (default: Kconfig)")
+
+    parser.add_argument(
+        "--out",
+        metavar="MINIMAL_CONFIGURATION",
+        default="defconfig",
+        help="Output filename for minimal configuration (default: defconfig)")
+
+    args = parser.parse_args()
+
+    kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True)
+    print(kconf.load_config())
+    print(kconf.write_min_config(args.out))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/Kconfiglib/setconfig.py b/scripts/Kconfiglib/setconfig.py
new file mode 100755
index 0000000..f9cf5cd
--- /dev/null
+++ b/scripts/Kconfiglib/setconfig.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019, Ulf Magnusson
+# SPDX-License-Identifier: ISC
+
+"""
+Simple utility for setting configuration values from the command line.
+
+Sample usage:
+
+  $ setconfig FOO_SUPPORT=y BAR_BITS=8
+
+Note: Symbol names should not be prefixed with 'CONFIG_'.
+
+The exit status on errors is 1.
+
+The default input/output configuration file is '.config'. A different filename
+can be passed in the KCONFIG_CONFIG environment variable.
+
+When overwriting a configuration file, the old version is saved to
+<filename>.old (e.g. .config.old).
+"""
+import argparse
+import sys
+
+import kconfiglib
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=__doc__)
+
+    parser.add_argument(
+        "--kconfig",
+        default="Kconfig",
+        help="Top-level Kconfig file (default: Kconfig)")
+
+    parser.add_argument(
+        "--no-check-exists",
+        dest="check_exists",
+        action="store_false",
+        help="Ignore assignments to non-existent symbols instead of erroring "
+             "out")
+
+    parser.add_argument(
+        "--no-check-value",
+        dest="check_value",
+        action="store_false",
+        help="Ignore assignments that didn't \"take\" (where the symbol got a "
+             "different value, e.g. due to unsatisfied dependencies) instead "
+             "of erroring out")
+
+    parser.add_argument(
+        "assignments",
+        metavar="ASSIGNMENT",
+        nargs="*",
+        help="A 'NAME=value' assignment")
+
+    args = parser.parse_args()
+
+    kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True)
+    print(kconf.load_config())
+
+    for arg in args.assignments:
+        if "=" not in arg:
+            sys.exit("error: no '=' in assignment: '{}'".format(arg))
+        name, value = arg.split("=", 1)
+
+        if name not in kconf.syms:
+            if not args.check_exists:
+                continue
+            sys.exit("error: no symbol '{}' in configuration".format(name))
+
+        sym = kconf.syms[name]
+
+        if not sym.set_value(value):
+            sys.exit("error: '{}' is an invalid value for the {} symbol {}"
+                     .format(value, kconfiglib.TYPE_TO_STR[sym.orig_type],
+                             name))
+
+        if args.check_value and sym.str_value != value:
+            sys.exit("error: {} was assigned the value '{}', but got the "
+                     "value '{}'. Check the symbol's dependencies, and make "
+                     "sure that it has a prompt."
+                     .format(name, value, sym.str_value))
+
+    print(kconf.write_config())
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/carray.sh b/scripts/carray.sh
index 0c52bd6..13f9d36 100755
--- a/scripts/carray.sh
+++ b/scripts/carray.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 function usage()
 {
diff --git a/scripts/create-binary-archive.sh b/scripts/create-binary-archive.sh
index 43c5452..9d56f2f 100755
--- a/scripts/create-binary-archive.sh
+++ b/scripts/create-binary-archive.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 function usage()
 {
@@ -103,7 +103,6 @@ build_opensbi() {
 		BUILD_PLATFORM_SUBDIR+=("kendryte/k210")
 		BUILD_PLATFORM_SUBDIR+=("fpga/ariane")
 		BUILD_PLATFORM_SUBDIR+=("fpga/openpiton")
-		BUILD_PLATFORM_SUBDIR+=("andes/ae350")
 		BUILD_PLATFORM_SUBDIR+=("generic")
 		;;
 	*)
@@ -118,7 +117,7 @@ build_opensbi() {
 	# Build and install generic library
 	echo "Build and install generic library XLEN=${BUILD_RISCV_XLEN}"
 	echo ""
-	make -C "${BUILD_OPENSBI_SOURCE_PATH}" O="${BUILD_OUTPUT_PATH}/${BUILD_NAME}" I="${BUILD_OUTPUT_PATH}/${BUILD_ARCHIVE_NAME}" PLATFORM_RISCV_XLEN="${BUILD_RISCV_XLEN}" install_libsbi install_libsbiutils -j "${BUILD_NUM_THREADS}"
+	make -C "${BUILD_OPENSBI_SOURCE_PATH}" O="${BUILD_OUTPUT_PATH}/${BUILD_NAME}" I="${BUILD_OUTPUT_PATH}/${BUILD_ARCHIVE_NAME}" PLATFORM_RISCV_XLEN="${BUILD_RISCV_XLEN}" install_libsbi -j "${BUILD_NUM_THREADS}"
 	echo ""
 
 	# Build and install relevant platforms
diff --git a/scripts/d2c.sh b/scripts/d2c.sh
index 33a3ccf..c411fcd 100755
--- a/scripts/d2c.sh
+++ b/scripts/d2c.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 function usage()
 {

More details

Full run details

Historical runs