Merge branch '2020-12-07-bootm-and-spl-atf-improvements' into next

- Series to improve "bootm" by allowing variable evaluation within the
  cmdline we would be passing.  This will help with Chrome OS but can be
  useful elsewhere.
- Improve ATF (TF-A) support within SPL.
diff --git a/arch/Kconfig b/arch/Kconfig
index 3aa99e0..356193f 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -146,6 +146,8 @@
 	imply ACPI_PMC_SANDBOX
 	imply CMD_PMC
 	imply CMD_CLONE
+	imply SILENT_CONSOLE
+	imply BOOTARGS_SUBST
 
 config SH
 	bool "SuperH architecture"
diff --git a/arch/arm/cpu/armv8/cpu-dt.c b/arch/arm/cpu/armv8/cpu-dt.c
index 97d4473..61c38b1 100644
--- a/arch/arm/cpu/armv8/cpu-dt.c
+++ b/arch/arm/cpu/armv8/cpu-dt.c
@@ -9,7 +9,7 @@
 #include <asm/system.h>
 #include <asm/armv8/sec_firmware.h>
 
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 int psci_update_dt(void *fdt)
 {
 	/*
diff --git a/arch/arm/cpu/armv8/fsl-layerscape/fdt.c b/arch/arm/cpu/armv8/fsl-layerscape/fdt.c
index 6d3391d..598ee2f 100644
--- a/arch/arm/cpu/armv8/fsl-layerscape/fdt.c
+++ b/arch/arm/cpu/armv8/fsl-layerscape/fdt.c
@@ -26,7 +26,7 @@
 #endif
 #include <fsl_sec.h>
 #include <asm/arch-fsl-layerscape/soc.h>
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 #include <asm/armv8/sec_firmware.h>
 #endif
 #include <asm/arch/speed.h>
@@ -81,7 +81,7 @@
 						    "device_type", "cpu", 4);
 	}
 
-#if defined(CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT) && \
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT) && \
 	defined(CONFIG_SEC_FIRMWARE_ARMV8_PSCI)
 	int node;
 	u32 psci_ver;
@@ -383,7 +383,7 @@
 }
 #endif
 
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 /* Remove JR node used by SEC firmware */
 void fdt_fixup_remove_jr(void *blob)
 {
@@ -488,7 +488,7 @@
 	else {
 		ccsr_sec_t __iomem *sec;
 
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 		fdt_fixup_remove_jr(blob);
 		fdt_fixup_kaslr(blob);
 #endif
diff --git a/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S b/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S
index a519f6e..d880373 100644
--- a/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S
+++ b/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S
@@ -192,6 +192,7 @@
 #endif
 
 	/* Initialize GIC Secure Bank Status */
+#if !defined(CONFIG_SPL_BUILD)
 #if defined(CONFIG_GICV2) || defined(CONFIG_GICV3)
 	branch_if_slave x0, 1f
 	bl	get_gic_offset
@@ -205,6 +206,7 @@
 	bl	gic_init_secure_percpu
 #endif
 #endif
+#endif
 
 100:
 	branch_if_master x0, x1, 2f
diff --git a/arch/arm/cpu/armv8/fsl-layerscape/ppa.c b/arch/arm/cpu/armv8/fsl-layerscape/ppa.c
index 1ddb267..2285296 100644
--- a/arch/arm/cpu/armv8/fsl-layerscape/ppa.c
+++ b/arch/arm/cpu/armv8/fsl-layerscape/ppa.c
@@ -16,7 +16,7 @@
 #elif defined(CONFIG_FSL_LSCH2)
 #include <asm/arch/immap_lsch2.h>
 #endif
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 #include <asm/armv8/sec_firmware.h>
 #endif
 #ifdef CONFIG_CHAIN_OF_TRUST
diff --git a/arch/arm/dts/fsl-ls1028a-kontron-sl28-u-boot.dtsi b/arch/arm/dts/fsl-ls1028a-kontron-sl28-u-boot.dtsi
index 2375549..65d5684 100644
--- a/arch/arm/dts/fsl-ls1028a-kontron-sl28-u-boot.dtsi
+++ b/arch/arm/dts/fsl-ls1028a-kontron-sl28-u-boot.dtsi
@@ -16,7 +16,7 @@
 		ethernet3 = &enetc6;
 	};
 
-	binman {
+	binman: binman {
 		filename = "u-boot.rom";
 		pad-byte = <0xff>;
 
@@ -80,21 +80,18 @@
 				conf-1 {
 					description = "fsl-ls1028a-kontron-sl28";
 					firmware = "uboot";
-					loadables = "uboot";
 					fdt = "fdt-1";
 				};
 
 				conf-2 {
 					description = "fsl-ls1028a-kontron-sl28-var3";
 					firmware = "uboot";
-					loadables = "uboot";
 					fdt = "fdt-2";
 				};
 
 				conf-3 {
 					description = "fsl-ls1028a-kontron-sl28-var4";
 					firmware = "uboot";
-					loadables = "uboot";
 					fdt = "fdt-3";
 				};
 			};
@@ -102,6 +99,81 @@
 	};
 };
 
+#ifdef CONFIG_SL28_SPL_LOADS_ATF_BL31
+&binman {
+	fit {
+		images {
+			bl31 {
+				description = "ARM Trusted Firmware (bl31)";
+				type = "firmware";
+				arch = "arm";
+				os = "arm-trusted-firmware";
+				compression = "none";
+				load = <CONFIG_SL28_BL31_ENTRY_ADDR>;
+				entry = <CONFIG_SL28_BL31_ENTRY_ADDR>;
+
+				blob-ext {
+					filename = "bl31.bin";
+				};
+			};
+		};
+
+		configurations {
+			conf-1 {
+				firmware = "bl31";
+				loadables = "uboot";
+			};
+
+			conf-2 {
+				firmware = "bl31";
+				loadables = "uboot";
+			};
+
+			conf-3 {
+				firmware = "bl31";
+				loadables = "uboot";
+			};
+		};
+	};
+};
+#endif
+
+#ifdef CONFIG_SL28_SPL_LOADS_OPTEE_BL32
+&binman {
+	fit {
+		images {
+			bl32 {
+				description = "OP-TEE Trusted OS (bl32)";
+				type = "firmware";
+				arch = "arm";
+				os = "tee";
+				compression = "none";
+				load = <CONFIG_SL28_BL32_ENTRY_ADDR>;
+				entry = <CONFIG_SL28_BL32_ENTRY_ADDR>;
+
+				blob-ext {
+					filename = "tee.bin";
+				};
+			};
+		};
+
+		configurations {
+			conf-1 {
+				loadables = "uboot", "bl32";
+			};
+
+			conf-2 {
+				loadables = "uboot", "bl32";
+			};
+
+			conf-3 {
+				loadables = "uboot", "bl32";
+			};
+		};
+	};
+};
+#endif
+
 &i2c0 {
 	rtc: rtc@32 {
 	};
diff --git a/arch/arm/lib/bootm-fdt.c b/arch/arm/lib/bootm-fdt.c
index 02a49a8..fe46a7d 100644
--- a/arch/arm/lib/bootm-fdt.c
+++ b/arch/arm/lib/bootm-fdt.c
@@ -63,7 +63,7 @@
 #endif
 
 #if defined(CONFIG_ARMV7_NONSEC) || defined(CONFIG_ARMV8_PSCI) || \
-	defined(CONFIG_SEC_FIRMWARE_ARMV8_PSCI)
+	CONFIG_IS_ENABLED(SEC_FIRMWARE_ARMV8_PSCI)
 	ret = psci_update_dt(blob);
 	if (ret)
 		return ret;
diff --git a/arch/arm/lib/psci-dt.c b/arch/arm/lib/psci-dt.c
index 0ed29a4..903b335 100644
--- a/arch/arm/lib/psci-dt.c
+++ b/arch/arm/lib/psci-dt.c
@@ -10,7 +10,7 @@
 #include <linux/sizes.h>
 #include <linux/kernel.h>
 #include <asm/psci.h>
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 #include <asm/armv8/sec_firmware.h>
 #endif
 
@@ -64,7 +64,7 @@
 		return nodeoff;
 
 init_psci_node:
-#ifdef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 	psci_ver = sec_firmware_support_psci_version();
 #elif defined(CONFIG_ARMV7_PSCI_1_0) || defined(CONFIG_ARMV8_PSCI)
 	psci_ver = ARM_PSCI_VER_1_0;
@@ -85,7 +85,7 @@
 			return tmp;
 	}
 
-#ifndef CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT
+#if !CONFIG_IS_ENABLED(ARMV8_SEC_FIRMWARE_SUPPORT)
 	/*
 	 * The Secure firmware framework isn't able to support PSCI version 0.1.
 	 */
diff --git a/arch/x86/lib/zimage.c b/arch/x86/lib/zimage.c
index 50fb16d..f154827 100644
--- a/arch/x86/lib/zimage.c
+++ b/arch/x86/lib/zimage.c
@@ -15,6 +15,7 @@
 #define LOG_CATEGORY	LOGC_BOOT
 
 #include <common.h>
+#include <bootm.h>
 #include <command.h>
 #include <env.h>
 #include <irq_func.h>
@@ -330,7 +331,12 @@
 	}
 
 	if (cmd_line) {
+		int max_size = 0xff;
+		int ret;
+
 		log_debug("Setup cmdline\n");
+		if (bootproto >= 0x0206)
+			max_size = hdr->cmdline_size;
 		if (bootproto >= 0x0202) {
 			hdr->cmd_line_ptr = (uintptr_t)cmd_line;
 		} else if (bootproto >= 0x0200) {
@@ -346,6 +352,14 @@
 			strcpy(cmd_line, (char *)cmdline_force);
 		else
 			build_command_line(cmd_line, auto_boot);
+		ret = bootm_process_cmdline(cmd_line, max_size, BOOTM_CL_ALL);
+		if (ret) {
+			printf("Cmdline setup failed (err=%d)\n", ret);
+			return ret;
+		}
+		printf("Kernel command line: \"");
+		puts(cmd_line);
+		printf("\"\n");
 	}
 
 	if (IS_ENABLED(CONFIG_INTEL_MID) && bootproto >= 0x0207)
diff --git a/board/kontron/sl28/Kconfig b/board/kontron/sl28/Kconfig
index cdec39b..4078ef1 100644
--- a/board/kontron/sl28/Kconfig
+++ b/board/kontron/sl28/Kconfig
@@ -15,4 +15,37 @@
 config SYS_TEXT_BASE
 	default 0x96000000
 
+config SL28_SPL_LOADS_ATF_BL31
+	bool "SPL loads BL31 of the ARM Trusted Firmware"
+	select SPL_ATF
+	select SPL_ATF_LOAD_IMAGE_V2
+	select ARMV8_SEC_FIRMWARE_SUPPORT
+	select SEC_FIRMWARE_ARMV8_PSCI
+	help
+	  Enable this to load a BL31 image by the SPL. You have to
+	  provde a bl31.bin in u-boot's root directory.
+
+if SL28_SPL_LOADS_ATF_BL31
+
+config SL28_BL31_ENTRY_ADDR
+	hex "Entry point of the BL31 image"
+	default 0xfbe00000
+
+endif
+
+config SL28_SPL_LOADS_OPTEE_BL32
+	bool "SPL loads OP-TEE Trusted OS as BL32"
+	depends on SL28_SPL_LOADS_ATF_BL31
+	help
+	  Enable this to load a BL32 image by the SPL. You have to
+	  provde a tee.bin in u-boot's root directory.
+
+if SL28_SPL_LOADS_OPTEE_BL32
+
+config SL28_BL32_ENTRY_ADDR
+	hex "Entry point of the BL32 image"
+	default 0xfc000000
+
+endif
+
 endif
diff --git a/board/kontron/sl28/Makefile b/board/kontron/sl28/Makefile
index 74d8012..5d220f0 100644
--- a/board/kontron/sl28/Makefile
+++ b/board/kontron/sl28/Makefile
@@ -5,4 +5,8 @@
 endif
 
 obj-y += common.o ddr.o
-obj-$(CONFIG_SPL_BUILD) += spl.o
+
+ifdef CONFIG_SPL_BUILD
+obj-y += spl.o
+obj-$(CONFIG_SPL_ATF) += spl_atf.o
+endif
diff --git a/board/kontron/sl28/sl28.c b/board/kontron/sl28/sl28.c
index b18127c..34f17b4 100644
--- a/board/kontron/sl28/sl28.c
+++ b/board/kontron/sl28/sl28.c
@@ -50,6 +50,7 @@
 	u64 base[CONFIG_NR_DRAM_BANKS];
 	u64 size[CONFIG_NR_DRAM_BANKS];
 	int nbanks = CONFIG_NR_DRAM_BANKS;
+	int node;
 	int i;
 
 	ft_cpu_setup(blob, bd);
@@ -64,5 +65,11 @@
 
 	fdt_fixup_icid(blob);
 
+	if (CONFIG_IS_ENABLED(SL28_SPL_LOADS_OPTEE_BL32)) {
+		node = fdt_node_offset_by_compatible(blob, -1, "linaro,optee-tz");
+		if (node)
+			fdt_set_node_status(blob, node, FDT_STATUS_OKAY, 0);
+	}
+
 	return 0;
 }
diff --git a/board/kontron/sl28/spl_atf.c b/board/kontron/sl28/spl_atf.c
new file mode 100644
index 0000000..5438b52
--- /dev/null
+++ b/board/kontron/sl28/spl_atf.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LS1028A TF-A calling support
+ *
+ * Copyright (c) 2020 Michael Walle <michael@walle.cc>
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <atf_common.h>
+#include <spl.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct region_info {
+	u64 addr;
+	u64 size;
+};
+
+struct dram_regions_info {
+	u64 num_dram_regions;
+	u64 total_dram_size;
+	struct region_info region[CONFIG_NR_DRAM_BANKS];
+};
+
+struct bl_params *bl2_plat_get_bl31_params_v2(uintptr_t bl32_entry,
+					      uintptr_t bl33_entry,
+					      uintptr_t fdt_addr)
+{
+	static struct dram_regions_info dram_regions_info = { 0 };
+	struct bl_params *bl_params;
+	struct bl_params_node *node;
+	void *dcfg_ccsr = (void *)DCFG_BASE;
+	int i;
+
+	dram_regions_info.num_dram_regions = CONFIG_NR_DRAM_BANKS;
+	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
+		dram_regions_info.region[i].addr = gd->bd->bi_dram[i].start;
+		dram_regions_info.region[i].size = gd->bd->bi_dram[i].size;
+		dram_regions_info.total_dram_size += gd->bd->bi_dram[i].size;
+	}
+
+	bl_params = bl2_plat_get_bl31_params_v2_default(bl32_entry, bl33_entry,
+							fdt_addr);
+
+	for_each_bl_params_node(bl_params, node) {
+		if (node->image_id == ATF_BL31_IMAGE_ID) {
+			node->ep_info->args.arg3 = (uintptr_t)&dram_regions_info;
+			node->ep_info->args.arg4 = in_le32(dcfg_ccsr + DCFG_PORSR1);
+		}
+	}
+
+	return bl_params;
+}
diff --git a/cmd/nvedit.c b/cmd/nvedit.c
index 7fce723..d0d2eca 100644
--- a/cmd/nvedit.c
+++ b/cmd/nvedit.c
@@ -266,7 +266,9 @@
 	/* Delete only ? */
 	if (argc < 3 || argv[2] == NULL) {
 		int rc = hdelete_r(name, &env_htab, env_flag);
-		return !rc;
+
+		/* If the variable didn't exist, don't report an error */
+		return rc && rc != -ENOENT ? 1 : 0;
 	}
 
 	/*
@@ -895,7 +897,7 @@
 	while (--argc > 0) {
 		char *name = *++argv;
 
-		if (!hdelete_r(name, &env_htab, env_flag))
+		if (hdelete_r(name, &env_htab, env_flag))
 			ret = 1;
 	}
 
diff --git a/cmd/pxe_utils.c b/cmd/pxe_utils.c
index 235522f..b9d9a57 100644
--- a/cmd/pxe_utils.c
+++ b/cmd/pxe_utils.c
@@ -322,7 +322,8 @@
 	if (label->append) {
 		char bootargs[CONFIG_SYS_CBSIZE];
 
-		cli_simple_process_macros(label->append, bootargs);
+		cli_simple_process_macros(label->append, bootargs,
+					  sizeof(bootargs));
 		env_set("bootargs", bootargs);
 	}
 
@@ -430,7 +431,8 @@
 		strcat(bootargs, ip_str);
 		strcat(bootargs, mac_str);
 
-		cli_simple_process_macros(bootargs, finalbootargs);
+		cli_simple_process_macros(bootargs, finalbootargs,
+					  sizeof(finalbootargs));
 		env_set("bootargs", finalbootargs);
 		printf("append: %s\n", finalbootargs);
 	}
diff --git a/common/Kconfig.boot b/common/Kconfig.boot
index 3f6d9c1..58e9854 100644
--- a/common/Kconfig.boot
+++ b/common/Kconfig.boot
@@ -865,6 +865,23 @@
 	  CONFIG_BOOTARGS goes into the environment value "bootargs". Note that
 	  this value will also override the "chosen" node in FDT blob.
 
+config BOOTARGS_SUBST
+	bool "Support substituting strings in boot arguments"
+	help
+	  This allows substituting string values in the boot arguments. These
+	  are applied after the commandline has been built.
+
+	  One use for this is to insert the root-disk UUID into the command
+	  line where bootargs contains "root=${uuid}"
+
+		setenv bootargs "console= root=${uuid}"
+		# Set the 'uuid' environment variable
+		part uuid mmc 2:2 uuid
+
+		# Command-line substitution will put the real uuid into the
+		# kernel command line
+		bootm
+
 config USE_BOOTCOMMAND
 	bool "Enable a default value for bootcmd"
 	help
diff --git a/common/bootm.c b/common/bootm.c
index 167eea4..8298693 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -7,6 +7,7 @@
 #ifndef USE_HOSTCC
 #include <common.h>
 #include <bootstage.h>
+#include <cli.h>
 #include <cpu_func.h>
 #include <env.h>
 #include <errno.h>
@@ -19,6 +20,7 @@
 #include <net.h>
 #include <asm/cache.h>
 #include <asm/io.h>
+#include <linux/sizes.h>
 #if defined(CONFIG_CMD_USB)
 #include <usb.h>
 #endif
@@ -35,6 +37,8 @@
 #define CONFIG_SYS_BOOTM_LEN	0x800000
 #endif
 
+#define MAX_CMDLINE_SIZE	SZ_4K
+
 #define IH_INITRD_ARCH IH_ARCH_DEFAULT
 
 #ifndef USE_HOSTCC
@@ -465,19 +469,35 @@
 	return iflag;
 }
 
-#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
+#define CONSOLE_ARG		"console="
+#define CONSOLE_ARG_SIZE	sizeof(CONSOLE_ARG)
 
-#define CONSOLE_ARG     "console="
-#define CONSOLE_ARG_LEN (sizeof(CONSOLE_ARG) - 1)
-
-static void fixup_silent_linux(void)
+/**
+ * fixup_silent_linux() - Handle silencing the linux boot if required
+ *
+ * This uses the silent_linux envvar to control whether to add/set a "console="
+ * parameter to the command line
+ *
+ * @buf: Buffer containing the string to process
+ * @maxlen: Maximum length of buffer
+ * @return 0 if OK, -ENOSPC if @maxlen is too small
+ */
+static int fixup_silent_linux(char *buf, int maxlen)
 {
-	char *buf;
-	const char *env_val;
-	char *cmdline = env_get("bootargs");
 	int want_silent;
+	char *cmdline;
+	int size;
 
 	/*
+	 * Move the input string to the end of buffer. The output string will be
+	 * built up at the start.
+	 */
+	size = strlen(buf) + 1;
+	if (size * 2 > maxlen)
+		return -ENOSPC;
+	cmdline = buf + maxlen - size;
+	memmove(cmdline, buf, size);
+	/*
 	 * Only fix cmdline when requested. The environment variable can be:
 	 *
 	 *	no - we never fixup
@@ -486,44 +506,132 @@
 	 */
 	want_silent = env_get_yesno("silent_linux");
 	if (want_silent == 0)
-		return;
+		return 0;
 	else if (want_silent == -1 && !(gd->flags & GD_FLG_SILENT))
-		return;
+		return 0;
 
 	debug("before silent fix-up: %s\n", cmdline);
-	if (cmdline && (cmdline[0] != '\0')) {
+	if (*cmdline) {
 		char *start = strstr(cmdline, CONSOLE_ARG);
 
-		/* Allocate space for maximum possible new command line */
-		buf = malloc(strlen(cmdline) + 1 + CONSOLE_ARG_LEN + 1);
-		if (!buf) {
-			debug("%s: out of memory\n", __func__);
-			return;
-		}
+		/* Check space for maximum possible new command line */
+		if (size + CONSOLE_ARG_SIZE > maxlen)
+			return -ENOSPC;
 
 		if (start) {
 			char *end = strchr(start, ' ');
-			int num_start_bytes = start - cmdline + CONSOLE_ARG_LEN;
+			int start_bytes;
 
-			strncpy(buf, cmdline, num_start_bytes);
+			start_bytes = start - cmdline + CONSOLE_ARG_SIZE - 1;
+			strncpy(buf, cmdline, start_bytes);
 			if (end)
-				strcpy(buf + num_start_bytes, end);
+				strcpy(buf + start_bytes, end);
 			else
-				buf[num_start_bytes] = '\0';
+				buf[start_bytes] = '\0';
 		} else {
 			sprintf(buf, "%s %s", cmdline, CONSOLE_ARG);
 		}
-		env_val = buf;
+		if (buf + strlen(buf) >= cmdline)
+			return -ENOSPC;
 	} else {
-		buf = NULL;
-		env_val = CONSOLE_ARG;
+		if (maxlen < sizeof(CONSOLE_ARG))
+			return -ENOSPC;
+		strcpy(buf, CONSOLE_ARG);
+	}
+	debug("after silent fix-up: %s\n", buf);
+
+	return 0;
+}
+
+/**
+ * process_subst() - Handle substitution of ${...} fields in the environment
+ *
+ * Handle variable substitution in the provided buffer
+ *
+ * @buf: Buffer containing the string to process
+ * @maxlen: Maximum length of buffer
+ * @return 0 if OK, -ENOSPC if @maxlen is too small
+ */
+static int process_subst(char *buf, int maxlen)
+{
+	char *cmdline;
+	int size;
+	int ret;
+
+	/* Move to end of buffer */
+	size = strlen(buf) + 1;
+	cmdline = buf + maxlen - size;
+	if (buf + size > cmdline)
+		return -ENOSPC;
+	memmove(cmdline, buf, size);
+
+	ret = cli_simple_process_macros(cmdline, buf, cmdline - buf);
+
+	return ret;
+}
+
+int bootm_process_cmdline(char *buf, int maxlen, int flags)
+{
+	int ret;
+
+	/* Check config first to enable compiler to eliminate code */
+	if (IS_ENABLED(CONFIG_SILENT_CONSOLE) &&
+	    !IS_ENABLED(CONFIG_SILENT_U_BOOT_ONLY) &&
+	    (flags & BOOTM_CL_SILENT)) {
+		ret = fixup_silent_linux(buf, maxlen);
+		if (ret)
+			return log_msg_ret("silent", ret);
+	}
+	if (IS_ENABLED(CONFIG_BOOTARGS_SUBST) && (flags & BOOTM_CL_SUBST)) {
+		ret = process_subst(buf, maxlen);
+		if (ret)
+			return log_msg_ret("silent", ret);
 	}
 
-	env_set("bootargs", env_val);
-	debug("after silent fix-up: %s\n", env_val);
-	free(buf);
+	return 0;
 }
-#endif /* CONFIG_SILENT_CONSOLE */
+
+int bootm_process_cmdline_env(int flags)
+{
+	const int maxlen = MAX_CMDLINE_SIZE;
+	bool do_silent;
+	const char *env;
+	char *buf;
+	int ret;
+
+	/* First check if any action is needed */
+	do_silent = IS_ENABLED(CONFIG_SILENT_CONSOLE) &&
+	    !IS_ENABLED(CONFIG_SILENT_U_BOOT_ONLY) && (flags & BOOTM_CL_SILENT);
+	if (!do_silent && !IS_ENABLED(CONFIG_BOOTARGS_SUBST))
+		return 0;
+
+	env = env_get("bootargs");
+	if (env && strlen(env) >= maxlen)
+		return -E2BIG;
+	buf = malloc(maxlen);
+	if (!buf)
+		return -ENOMEM;
+	if (env)
+		strcpy(buf, env);
+	else
+		*buf = '\0';
+	ret = bootm_process_cmdline(buf, maxlen, flags);
+	if (!ret) {
+		ret = env_set("bootargs", buf);
+
+		/*
+		 * If buf is "" and bootargs does not exist, this will produce
+		 * an error trying to delete bootargs. Ignore it
+		 */
+		if (ret == -ENOENT)
+			ret = 0;
+	}
+	free(buf);
+	if (ret)
+		return log_msg_ret("env", ret);
+
+	return 0;
+}
 
 /**
  * Execute selected states of the bootm command.
@@ -627,10 +735,12 @@
 	if (!ret && (states & BOOTM_STATE_OS_BD_T))
 		ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
 	if (!ret && (states & BOOTM_STATE_OS_PREP)) {
-#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
-		if (images->os.os == IH_OS_LINUX)
-			fixup_silent_linux();
-#endif
+		ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX);
+		if (ret) {
+			printf("Cmdline setup failed (err=%d)\n", ret);
+			ret = CMD_RET_FAILURE;
+			goto err;
+		}
 		ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
 	}
 
diff --git a/common/cli_simple.c b/common/cli_simple.c
index 7d91316..e80ba48 100644
--- a/common/cli_simple.c
+++ b/common/cli_simple.c
@@ -60,13 +60,14 @@
 	return nargs;
 }
 
-void cli_simple_process_macros(const char *input, char *output)
+int cli_simple_process_macros(const char *input, char *output, int max_size)
 {
 	char c, prev;
 	const char *varname_start = NULL;
 	int inputcnt = strlen(input);
-	int outputcnt = CONFIG_SYS_CBSIZE;
+	int outputcnt = max_size;
 	int state = 0;		/* 0 = waiting for '$'  */
+	int ret;
 
 	/* 1 = waiting for '(' or '{' */
 	/* 2 = waiting for ')' or '}' */
@@ -157,13 +158,18 @@
 		prev = c;
 	}
 
-	if (outputcnt)
+	ret = inputcnt ? -ENOSPC : 0;
+	if (outputcnt) {
 		*output = 0;
-	else
+	} else {
 		*(output - 1) = 0;
+		ret = -ENOSPC;
+	}
 
 	debug_parser("[PROCESS_MACROS] OUTPUT len %zd: \"%s\"\n",
 		     strlen(output_start), output_start);
+
+	return ret;
 }
 
  /*
@@ -239,7 +245,8 @@
 		debug_parser("token: \"%s\"\n", token);
 
 		/* find macros in this token and replace them */
-		cli_simple_process_macros(token, finaltoken);
+		cli_simple_process_macros(token, finaltoken,
+					  sizeof(finaltoken));
 
 		/* Extract arguments */
 		argc = cli_simple_parse_line(finaltoken, argv);
diff --git a/common/spl/Kconfig b/common/spl/Kconfig
index d8086bd..6d980be 100644
--- a/common/spl/Kconfig
+++ b/common/spl/Kconfig
@@ -1276,6 +1276,15 @@
 	  is loaded by SPL (which is considered as BL2 in ATF terminology).
 	  More detail at: https://github.com/ARM-software/arm-trusted-firmware
 
+config SPL_ATF_LOAD_IMAGE_V2
+	bool "Use the new LOAD_IMAGE_V2 parameter passing"
+	depends on SPL_ATF
+	help
+	  Some platforms use the newer LOAD_IMAGE_V2 parameter passing.
+
+	  If you want to load a bl31 image from the SPL and need the new
+	  method, say Y.
+
 config SPL_ATF_NO_PLATFORM_PARAM
         bool "Pass no platform parameter"
 	depends on SPL_ATF
diff --git a/common/spl/spl_atf.c b/common/spl/spl_atf.c
index 9bd25f6..e1b68dd 100644
--- a/common/spl/spl_atf.c
+++ b/common/spl/spl_atf.c
@@ -18,13 +18,36 @@
 #include <spl.h>
 #include <asm/cache.h>
 
-static struct bl2_to_bl31_params_mem bl31_params_mem;
-static struct bl31_params *bl2_to_bl31_params;
+/* Holds all the structures we need for bl31 parameter passing */
+struct bl2_to_bl31_params_mem {
+	struct bl31_params bl31_params;
+	struct atf_image_info bl31_image_info;
+	struct atf_image_info bl32_image_info;
+	struct atf_image_info bl33_image_info;
+	struct entry_point_info bl33_ep_info;
+	struct entry_point_info bl32_ep_info;
+	struct entry_point_info bl31_ep_info;
+};
 
-__weak struct bl31_params *bl2_plat_get_bl31_params(uintptr_t bl32_entry,
-						    uintptr_t bl33_entry,
-						    uintptr_t fdt_addr)
+struct bl2_to_bl31_params_mem_v2 {
+	struct bl_params bl_params;
+	struct bl_params_node bl31_params_node;
+	struct bl_params_node bl32_params_node;
+	struct bl_params_node bl33_params_node;
+	struct atf_image_info bl31_image_info;
+	struct atf_image_info bl32_image_info;
+	struct atf_image_info bl33_image_info;
+	struct entry_point_info bl33_ep_info;
+	struct entry_point_info bl32_ep_info;
+	struct entry_point_info bl31_ep_info;
+};
+
+struct bl31_params *bl2_plat_get_bl31_params_default(uintptr_t bl32_entry,
+						     uintptr_t bl33_entry,
+						     uintptr_t fdt_addr)
 {
+	static struct bl2_to_bl31_params_mem bl31_params_mem;
+	struct bl31_params *bl2_to_bl31_params;
 	struct entry_point_info *bl32_ep_info;
 	struct entry_point_info *bl33_ep_info;
 
@@ -78,6 +101,87 @@
 	return bl2_to_bl31_params;
 }
 
+__weak struct bl31_params *bl2_plat_get_bl31_params(uintptr_t bl32_entry,
+						    uintptr_t bl33_entry,
+						    uintptr_t fdt_addr)
+{
+	return bl2_plat_get_bl31_params_default(bl32_entry, bl33_entry,
+						fdt_addr);
+}
+
+struct bl_params *bl2_plat_get_bl31_params_v2_default(uintptr_t bl32_entry,
+						      uintptr_t bl33_entry,
+						      uintptr_t fdt_addr)
+{
+	static struct bl2_to_bl31_params_mem_v2 bl31_params_mem;
+	struct bl_params *bl_params;
+	struct bl_params_node *bl_params_node;
+
+	/*
+	 * Initialise the memory for all the arguments that needs to
+	 * be passed to BL31
+	 */
+	memset(&bl31_params_mem, 0, sizeof(bl31_params_mem));
+
+	/* Assign memory for TF related information */
+	bl_params = &bl31_params_mem.bl_params;
+	SET_PARAM_HEAD(bl_params, ATF_PARAM_BL_PARAMS, ATF_VERSION_2, 0);
+	bl_params->head = &bl31_params_mem.bl31_params_node;
+
+	/* Fill BL31 related information */
+	bl_params_node = &bl31_params_mem.bl31_params_node;
+	bl_params_node->image_id = ATF_BL31_IMAGE_ID;
+	bl_params_node->image_info = &bl31_params_mem.bl31_image_info;
+	bl_params_node->ep_info = &bl31_params_mem.bl31_ep_info;
+	bl_params_node->next_params_info = &bl31_params_mem.bl32_params_node;
+	SET_PARAM_HEAD(bl_params_node->image_info, ATF_PARAM_IMAGE_BINARY,
+		       ATF_VERSION_2, 0);
+
+	/* Fill BL32 related information */
+	bl_params_node = &bl31_params_mem.bl32_params_node;
+	bl_params_node->image_id = ATF_BL32_IMAGE_ID;
+	bl_params_node->image_info = &bl31_params_mem.bl32_image_info;
+	bl_params_node->ep_info = &bl31_params_mem.bl32_ep_info;
+	bl_params_node->next_params_info = &bl31_params_mem.bl33_params_node;
+	SET_PARAM_HEAD(bl_params_node->ep_info, ATF_PARAM_EP,
+		       ATF_VERSION_2, ATF_EP_SECURE);
+
+	/* secure payload is optional, so set pc to 0 if absent */
+	bl_params_node->ep_info->args.arg3 = fdt_addr;
+	bl_params_node->ep_info->pc = bl32_entry ? bl32_entry : 0;
+	bl_params_node->ep_info->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX,
+						DISABLE_ALL_EXECPTIONS);
+	SET_PARAM_HEAD(bl_params_node->image_info, ATF_PARAM_IMAGE_BINARY,
+		       ATF_VERSION_2, 0);
+
+	/* Fill BL33 related information */
+	bl_params_node = &bl31_params_mem.bl33_params_node;
+	bl_params_node->image_id = ATF_BL33_IMAGE_ID;
+	bl_params_node->image_info = &bl31_params_mem.bl33_image_info;
+	bl_params_node->ep_info = &bl31_params_mem.bl33_ep_info;
+	bl_params_node->next_params_info = NULL;
+	SET_PARAM_HEAD(bl_params_node->ep_info, ATF_PARAM_EP,
+		       ATF_VERSION_2, ATF_EP_NON_SECURE);
+
+	/* BL33 expects to receive the primary CPU MPID (through x0) */
+	bl_params_node->ep_info->args.arg0 = 0xffff & read_mpidr();
+	bl_params_node->ep_info->pc = bl33_entry;
+	bl_params_node->ep_info->spsr = SPSR_64(MODE_EL2, MODE_SP_ELX,
+						DISABLE_ALL_EXECPTIONS);
+	SET_PARAM_HEAD(bl_params_node->image_info, ATF_PARAM_IMAGE_BINARY,
+		       ATF_VERSION_2, 0);
+
+	return bl_params;
+}
+
+__weak struct bl_params *bl2_plat_get_bl31_params_v2(uintptr_t bl32_entry,
+						     uintptr_t bl33_entry,
+						     uintptr_t fdt_addr)
+{
+	return bl2_plat_get_bl31_params_v2_default(bl32_entry, bl33_entry,
+						   fdt_addr);
+}
+
 static inline void raw_write_daif(unsigned int daif)
 {
 	__asm__ __volatile__("msr DAIF, %0\n\t" : : "r" (daif) : "memory");
@@ -88,16 +192,21 @@
 static void bl31_entry(uintptr_t bl31_entry, uintptr_t bl32_entry,
 		       uintptr_t bl33_entry, uintptr_t fdt_addr)
 {
-	struct bl31_params *bl31_params;
 	atf_entry_t  atf_entry = (atf_entry_t)bl31_entry;
+	void *bl31_params;
 
-	bl31_params = bl2_plat_get_bl31_params(bl32_entry, bl33_entry,
-					       fdt_addr);
+	if (CONFIG_IS_ENABLED(ATF_LOAD_IMAGE_V2))
+		bl31_params = bl2_plat_get_bl31_params_v2(bl32_entry,
+							  bl33_entry,
+							  fdt_addr);
+	else
+		bl31_params = bl2_plat_get_bl31_params(bl32_entry, bl33_entry,
+						       fdt_addr);
 
 	raw_write_daif(SPSR_EXCEPTION_MASK);
 	dcache_disable();
 
-	atf_entry((void *)bl31_params, (void *)fdt_addr);
+	atf_entry(bl31_params, (void *)fdt_addr);
 }
 
 static int spl_fit_images_find(void *blob, int os)
diff --git a/include/atf_common.h b/include/atf_common.h
index fd5454c..d69892f 100644
--- a/include/atf_common.h
+++ b/include/atf_common.h
@@ -14,8 +14,14 @@
 #define ATF_PARAM_EP		0x01
 #define ATF_PARAM_IMAGE_BINARY	0x02
 #define ATF_PARAM_BL31		0x03
+#define ATF_PARAM_BL_PARAMS	0x05
 
 #define ATF_VERSION_1	0x01
+#define ATF_VERSION_2	0x02
+
+#define ATF_BL31_IMAGE_ID	0x03
+#define ATF_BL32_IMAGE_ID	0x04
+#define ATF_BL33_IMAGE_ID	0x05
 
 #define ATF_EP_SECURE	0x0
 #define ATF_EP_NON_SECURE	0x1
@@ -121,6 +127,9 @@
 	struct param_header h;
 	uintptr_t image_base;   /* physical address of base of image */
 	uint32_t image_size;    /* bytes read from image file */
+#if CONFIG_IS_ENABLED(ATF_LOAD_IMAGE_V2)
+	uint32_t image_max_size;
+#endif
 };
 
 /*****************************************************************************
@@ -162,21 +171,28 @@
 	struct atf_image_info *bl33_image_info;
 };
 
-/*******************************************************************************
- * This structure represents the superset of information that is passed to
- * BL31, e.g. while passing control to it from BL2, bl31_params
- * and other platform specific params
- ******************************************************************************/
-struct bl2_to_bl31_params_mem {
-	struct bl31_params bl31_params;
-	struct atf_image_info bl31_image_info;
-	struct atf_image_info bl32_image_info;
-	struct atf_image_info bl33_image_info;
-	struct entry_point_info bl33_ep_info;
-	struct entry_point_info bl32_ep_info;
-	struct entry_point_info bl31_ep_info;
+/* BL image node in the BL image execution sequence */
+struct bl_params_node {
+	unsigned int image_id;
+	struct atf_image_info *image_info;
+	struct entry_point_info *ep_info;
+	struct bl_params_node *next_params_info;
 };
 
+/*
+ * BL image head node in the BL image execution sequence
+ * It is also used to pass information to next BL image.
+ */
+struct bl_params {
+	struct param_header h;
+	struct bl_params_node *head;
+};
+
+#define for_each_bl_params_node(bl_params, node) \
+	for ((node) = (bl_params)->head; \
+	     (node); \
+	     (node) = (node)->next_params_info)
+
 #endif /*__ASSEMBLY__ */
 
 #endif /* __BL_COMMON_H__ */
diff --git a/include/bootm.h b/include/bootm.h
index a812a6b..7f88ec7 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -75,6 +75,14 @@
  */
 void switch_to_non_secure_mode(void);
 
+/* Flags to control bootm_process_cmdline() */
+enum bootm_cmdline_t {
+	BOOTM_CL_SILENT	= 1 << 0,	/* Do silent console processing */
+	BOOTM_CL_SUBST	= 1 << 1,	/* Do substitution */
+
+	BOOTM_CL_ALL	= 3,		/* All substitutions */
+};
+
 /**
  * arch_preboot_os() - arch specific configuration before booting
  */
@@ -85,4 +93,36 @@
  */
 void board_preboot_os(void);
 
+/*
+ * bootm_process_cmdline() - Process fix-ups for the command line
+ *
+ * This handles:
+ *
+ *  - making Linux boot silently if requested ('silent_linux' envvar)
+ *  - performing substitutions in the command line ('bootargs_subst' envvar)
+ *
+ * @maxlen must provide enough space for the string being processed plus the
+ * resulting string
+ *
+ * @buf: buffer holding commandline string to adjust
+ * @maxlen: Maximum length of buffer at @buf (including \0)
+ * @flags: Flags to control what happens (see bootm_cmdline_t)
+ * @return 0 if OK, -ENOMEM if out of memory, -ENOSPC if the commandline is too
+ *	long
+ */
+int bootm_process_cmdline(char *buf, int maxlen, int flags);
+
+/**
+ * bootm_process_cmdline_env() - Process fix-ups for the command line
+ *
+ * Updates the 'bootargs' envvar as required. This handles:
+ *
+ *  - making Linux boot silently if requested ('silent_linux' envvar)
+ *  - performing substitutions in the command line ('bootargs_subst' envvar)
+ *
+ * @flags: Flags to control what happens (see bootm_cmdline_t)
+ * @return 0 if OK, -ENOMEM if out of memory
+ */
+int bootm_process_cmdline_env(int flags);
+
 #endif
diff --git a/include/cli.h b/include/cli.h
index 39b9137..3449fa6 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -34,8 +34,10 @@
  *
  * @param input		Input string possible containing $() / ${} vars
  * @param output	Output string with $() / ${} vars expanded
+ * @param max_size	Maximum size of @output (including terminator)
+ * @return 0 if OK, -ENOSPC if we ran out of space in @output
  */
-void cli_simple_process_macros(const char *input, char *output);
+int cli_simple_process_macros(const char *input, char *output, int max_size);
 
 /**
  * cli_simple_run_command_list() - Execute a list of command
diff --git a/include/search.h b/include/search.h
index e56843c..d0bb443 100644
--- a/include/search.h
+++ b/include/search.h
@@ -80,7 +80,16 @@
 int hmatch_r(const char *match, int last_idx, struct env_entry **retval,
 	     struct hsearch_data *htab);
 
-/* Search and delete entry matching "key" in internal hash table. */
+/**
+ * hdelete_r() - Search and delete entry in internal hash table
+ *
+ * @key: Name of entry to delete
+ * @htab: Hash table
+ * @flag: Flags to use (H_...)
+ * @return 0 on success, -ENOENT if not found, -EPERM if the hash table callback
+ *	rejected changing the variable, -EINVAL if the hash table refused to
+ *	delete the variable
+ */
 int hdelete_r(const char *key, struct hsearch_data *htab, int flag);
 
 ssize_t hexport_r(struct hsearch_data *htab, const char sep, int flag,
diff --git a/include/spl.h b/include/spl.h
index b72dfc7..374a295 100644
--- a/include/spl.h
+++ b/include/spl.h
@@ -526,26 +526,80 @@
 void spl_invoke_atf(struct spl_image_info *spl_image);
 
 /**
- * bl2_plat_get_bl31_params() - prepare params for bl31.
- * @bl32_entry	address of BL32 executable (secure)
- * @bl33_entry	address of BL33 executable (non secure)
- * @fdt_addr	address of Flat Device Tree
+ * bl2_plat_get_bl31_params() - return params for bl31.
+ * @bl32_entry:	address of BL32 executable (secure)
+ * @bl33_entry:	address of BL33 executable (non secure)
+ * @fdt_addr:	address of Flat Device Tree
  *
- * This function assigns a pointer to the memory that the platform has kept
- * aside to pass platform specific and trusted firmware related information
- * to BL31. This memory is allocated by allocating memory to
- * bl2_to_bl31_params_mem structure which is a superset of all the
- * structure whose information is passed to BL31
- * NOTE: This function should be called only once and should be done
- * before generating params to BL31
+ * This is a weak function which might be overridden by the board code. By
+ * default it will just call bl2_plat_get_bl31_params_default().
  *
- * @return bl31 params structure pointer
+ * If you just want to manipulate or add some parameters, you can override
+ * this function, call bl2_plat_get_bl31_params_default and operate on the
+ * returned bl31 params.
+ *
+ * Return: bl31 params structure pointer
  */
 struct bl31_params *bl2_plat_get_bl31_params(uintptr_t bl32_entry,
 					     uintptr_t bl33_entry,
 					     uintptr_t fdt_addr);
 
 /**
+ * bl2_plat_get_bl31_params_default() - prepare params for bl31.
+ * @bl32_entry:	address of BL32 executable (secure)
+ * @bl33_entry:	address of BL33 executable (non secure)
+ * @fdt_addr:	address of Flat Device Tree
+ *
+ * This is the default implementation of bl2_plat_get_bl31_params(). It assigns
+ * a pointer to the memory that the platform has kept aside to pass platform
+ * specific and trusted firmware related information to BL31. This memory is
+ * allocated by allocating memory to bl2_to_bl31_params_mem structure which is
+ * a superset of all the structure whose information is passed to BL31
+ *
+ * NOTE: The memory is statically allocated, thus this function should be
+ * called only once. All subsequent calls will overwrite any changes.
+ *
+ * Return: bl31 params structure pointer
+ */
+struct bl31_params *bl2_plat_get_bl31_params_default(uintptr_t bl32_entry,
+						     uintptr_t bl33_entry,
+						     uintptr_t fdt_addr);
+
+/**
+ * bl2_plat_get_bl31_params_v2() - return params for bl31
+ * @bl32_entry:	address of BL32 executable (secure)
+ * @bl33_entry:	address of BL33 executable (non secure)
+ * @fdt_addr:	address of Flat Device Tree
+ *
+ * This function does the same as bl2_plat_get_bl31_params() except that is is
+ * used for the new LOAD_IMAGE_V2 option, which uses a slightly different
+ * method to pass the parameters.
+ *
+ * Return: bl31 params structure pointer
+ */
+struct bl_params *bl2_plat_get_bl31_params_v2(uintptr_t bl32_entry,
+					      uintptr_t bl33_entry,
+					      uintptr_t fdt_addr);
+
+/**
+ * bl2_plat_get_bl31_params_v2_default() - prepare params for bl31.
+ * @bl32_entry:	address of BL32 executable (secure)
+ * @bl33_entry:	address of BL33 executable (non secure)
+ * @fdt_addr:	address of Flat Device Tree
+ *
+ * This is the default implementation of bl2_plat_get_bl31_params_v2(). It
+ * prepares the linked list of the bl31 params, populates the image types and
+ * set the entry points for bl32 and bl33 (if available).
+ *
+ * NOTE: The memory is statically allocated, thus this function should be
+ * called only once. All subsequent calls will overwrite any changes.
+ *
+ * Return: bl31 params structure pointer
+ */
+struct bl_params *bl2_plat_get_bl31_params_v2_default(uintptr_t bl32_entry,
+						      uintptr_t bl33_entry,
+						      uintptr_t fdt_addr);
+/**
  * spl_optee_entry - entry function for optee
  *
  * args defind in op-tee project
diff --git a/include/test/suites.h b/include/test/suites.h
index 5c97846..52e8fc8 100644
--- a/include/test/suites.h
+++ b/include/test/suites.h
@@ -26,6 +26,7 @@
 		    struct unit_test *tests, int n_ents,
 		    int argc, char *const argv[]);
 
+int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc,
 		   char *const argv[]);
 int do_ut_compression(struct cmd_tbl *cmdtp, int flag, int argc,
diff --git a/lib/hashtable.c b/lib/hashtable.c
index 7c08f5c..ff5ff72 100644
--- a/lib/hashtable.c
+++ b/lib/hashtable.c
@@ -472,7 +472,7 @@
 	idx = hsearch_r(e, ENV_FIND, &ep, htab, 0);
 	if (idx == 0) {
 		__set_errno(ESRCH);
-		return 0;	/* not found */
+		return -ENOENT;	/* not found */
 	}
 
 	/* Check for permission */
@@ -481,7 +481,7 @@
 		debug("change_ok() rejected deleting variable "
 			"%s, skipping it!\n", key);
 		__set_errno(EPERM);
-		return 0;
+		return -EPERM;
 	}
 
 	/* If there is a callback, call it */
@@ -490,12 +490,12 @@
 		debug("callback() rejected deleting variable "
 			"%s, skipping it!\n", key);
 		__set_errno(EINVAL);
-		return 0;
+		return -EINVAL;
 	}
 
 	_hdelete(key, htab, ep, idx);
 
-	return 1;
+	return 0;
 }
 
 #if !(defined(CONFIG_SPL_BUILD) && !defined(CONFIG_SPL_SAVEENV))
@@ -917,7 +917,7 @@
 			if (!drop_var_from_set(name, nvars, localvars))
 				continue;
 
-			if (hdelete_r(name, htab, flag) == 0)
+			if (hdelete_r(name, htab, flag))
 				debug("DELETE ERROR ##############################\n");
 
 			continue;
@@ -979,7 +979,7 @@
 		 * b) if the variable was not present in current env, we notify
 		 *    it might be a typo
 		 */
-		if (hdelete_r(localvars[i], htab, flag) == 0)
+		if (hdelete_r(localvars[i], htab, flag))
 			printf("WARNING: '%s' neither in running nor in imported env!\n", localvars[i]);
 		else
 			printf("WARNING: '%s' not in imported env, deleting it!\n", localvars[i]);
diff --git a/test/Makefile b/test/Makefile
index 8296734..d4323f9 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -5,6 +5,7 @@
 ifneq ($(CONFIG_SANDBOX),)
 obj-$(CONFIG_$(SPL_)CMDLINE) += bloblist.o
 endif
+obj-$(CONFIG_$(SPL_)CMDLINE) += bootm.o
 obj-$(CONFIG_$(SPL_)CMDLINE) += cmd/
 obj-$(CONFIG_$(SPL_)CMDLINE) += cmd_ut.o
 obj-$(CONFIG_$(SPL_)CMDLINE) += command_ut.o
diff --git a/test/bootm.c b/test/bootm.c
new file mode 100644
index 0000000..92dc2b6
--- /dev/null
+++ b/test/bootm.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for bootm routines
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#include <common.h>
+#include <bootm.h>
+#include <test/suites.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define BOOTM_TEST(_name, _flags)	UNIT_TEST(_name, _flags, bootm_test)
+
+enum {
+	BUF_SIZE	= 1024,
+};
+
+#define CONSOLE_STR	"console=/dev/ttyS0"
+
+/* Test cmdline processing where nothing happens */
+static int bootm_test_nop(struct unit_test_state *uts)
+{
+	char buf[BUF_SIZE];
+
+	*buf = '\0';
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, true));
+	ut_asserteq_str("", buf);
+
+	strcpy(buf, "test");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, true));
+	ut_asserteq_str("test", buf);
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_nop, 0);
+
+/* Test cmdline processing when out of space */
+static int bootm_test_nospace(struct unit_test_state *uts)
+{
+	char buf[BUF_SIZE];
+
+	/* Zero buffer size */
+	*buf = '\0';
+	ut_asserteq(-ENOSPC, bootm_process_cmdline(buf, 0, true));
+
+	/* Buffer string not terminated */
+	memset(buf, 'a', BUF_SIZE);
+	ut_asserteq(-ENOSPC, bootm_process_cmdline(buf, BUF_SIZE, true));
+
+	/* Not enough space to copy string */
+	memset(buf, '\0', BUF_SIZE);
+	memset(buf, 'a', BUF_SIZE / 2);
+	ut_asserteq(-ENOSPC, bootm_process_cmdline(buf, BUF_SIZE, true));
+
+	/* Just enough space */
+	memset(buf, '\0', BUF_SIZE);
+	memset(buf, 'a', BUF_SIZE / 2 - 1);
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, true));
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_nospace, 0);
+
+/* Test silent processing */
+static int bootm_test_silent(struct unit_test_state *uts)
+{
+	char buf[BUF_SIZE];
+
+	/* 'silent_linux' not set should do nothing */
+	env_set("silent_linux", NULL);
+	strcpy(buf, CONSOLE_STR);
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SILENT));
+	ut_asserteq_str(CONSOLE_STR, buf);
+
+	ut_assertok(env_set("silent_linux", "no"));
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SILENT));
+	ut_asserteq_str(CONSOLE_STR, buf);
+
+	ut_assertok(env_set("silent_linux", "yes"));
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SILENT));
+	ut_asserteq_str("console=", buf);
+
+	/* Empty buffer should still add the string */
+	*buf = '\0';
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SILENT));
+	ut_asserteq_str("console=", buf);
+
+	/* Check nothing happens when do_silent is false */
+	*buf = '\0';
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, 0));
+	ut_asserteq_str("", buf);
+
+	/* Not enough space */
+	*buf = '\0';
+	ut_asserteq(-ENOSPC, bootm_process_cmdline(buf, 8, BOOTM_CL_SILENT));
+
+	/* Just enough space */
+	*buf = '\0';
+	ut_assertok(bootm_process_cmdline(buf, 9, BOOTM_CL_SILENT));
+
+	/* add at end */
+	strcpy(buf, "something");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SILENT));
+	ut_asserteq_str("something console=", buf);
+
+	/* change at start */
+	strcpy(buf, CONSOLE_STR " something");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SILENT));
+	ut_asserteq_str("console= something", buf);
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_silent, 0);
+
+/* Test substitution processing */
+static int bootm_test_subst(struct unit_test_state *uts)
+{
+	char buf[BUF_SIZE];
+
+	/* try with an unset variable */
+	ut_assertok(env_set("var", NULL));
+	strcpy(buf, "some${var}thing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("something", buf);
+
+	/* Replace with shorter string */
+	ut_assertok(env_set("var", "bb"));
+	strcpy(buf, "some${var}thing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("somebbthing", buf);
+
+	/* Replace with same-length string */
+	ut_assertok(env_set("var", "abc"));
+	strcpy(buf, "some${var}thing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing", buf);
+
+	/* Replace with longer string */
+	ut_assertok(env_set("var", "abcde"));
+	strcpy(buf, "some${var}thing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcdething", buf);
+
+	/* Check it is case sensitive */
+	ut_assertok(env_set("VAR", NULL));
+	strcpy(buf, "some${VAR}thing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("something", buf);
+
+	/* Check too long - need 12 bytes for each string */
+	strcpy(buf, "some${var}thing");
+	ut_asserteq(-ENOSPC,
+		    bootm_process_cmdline(buf, 12 * 2 - 1, BOOTM_CL_SUBST));
+
+	/* Check just enough space */
+	strcpy(buf, "some${var}thing");
+	ut_assertok(bootm_process_cmdline(buf, 16 * 2, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcdething", buf);
+
+	/*
+	 * Check the substition string being too long. This results in a string
+	 * of 12 (13 bytes). We need enough space for that plus the original
+	 * "a${var}c" string of 9 bytes. So 12 + 9 = 21 bytes.
+	 */
+	ut_assertok(env_set("var", "1234567890"));
+	strcpy(buf, "a${var}c");
+	ut_asserteq(-ENOSPC, bootm_process_cmdline(buf, 21, BOOTM_CL_SUBST));
+
+	strcpy(buf, "a${var}c");
+	ut_asserteq(0, bootm_process_cmdline(buf, 22, BOOTM_CL_SUBST));
+
+	/* Check multiple substitutions */
+	ut_assertok(env_set("var", "abc"));
+	strcpy(buf, "some${var}thing${bvar}else");
+	ut_asserteq(0, bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthingelse", buf);
+
+	/* Check multiple substitutions */
+	ut_assertok(env_set("bvar", "123"));
+	strcpy(buf, "some${var}thing${bvar}else");
+	ut_asserteq(0, bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing123else", buf);
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_subst, 0);
+
+/* Test silent processing in the bootargs variable */
+static int bootm_test_silent_var(struct unit_test_state *uts)
+{
+	env_set("bootargs", NULL);
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SUBST));
+	ut_assertnull(env_get("bootargs"));
+
+	ut_assertok(env_set("bootargs", "some${var}thing"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SUBST));
+	ut_asserteq_str("something", env_get("bootargs"));
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_silent_var, 0);
+
+/* Test substitution processing in the bootargs variable */
+static int bootm_test_subst_var(struct unit_test_state *uts)
+{
+	env_set("bootargs", NULL);
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
+	ut_asserteq_str("console=", env_get("bootargs"));
+
+	ut_assertok(env_set("var", "abc"));
+	ut_assertok(env_set("bootargs", "some${var}thing"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
+	ut_asserteq_str("some${var}thing console=", env_get("bootargs"));
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_subst_var, 0);
+
+/* Test substitution and silent console processing in the bootargs variable */
+static int bootm_test_subst_both(struct unit_test_state *uts)
+{
+	ut_assertok(env_set("silent_linux", "yes"));
+	env_set("bootargs", NULL);
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_ALL));
+	ut_asserteq_str("console=", env_get("bootargs"));
+
+	ut_assertok(env_set("bootargs", "some${var}thing " CONSOLE_STR));
+	ut_assertok(env_set("var", "1234567890"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_ALL));
+	ut_asserteq_str("some1234567890thing console=", env_get("bootargs"));
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_subst_both, 0);
+
+int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	struct unit_test *tests = ll_entry_start(struct unit_test, bootm_test);
+	const int n_ents = ll_entry_count(struct unit_test, bootm_test);
+
+	return cmd_ut_category("bootm", "bootm_test_", tests, n_ents,
+			       argc, argv);
+}
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index f79109e..fad1c89 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -88,6 +88,7 @@
 			 "", ""),
 	U_BOOT_CMD_MKENT(bloblist, CONFIG_SYS_MAXARGS, 1, do_ut_bloblist,
 			 "", ""),
+	U_BOOT_CMD_MKENT(bootm, CONFIG_SYS_MAXARGS, 1, do_ut_bootm, "", ""),
 	U_BOOT_CMD_MKENT(str, CONFIG_SYS_MAXARGS, 1, do_ut_str,
 			 "", ""),
 #endif
diff --git a/test/env/hashtable.c b/test/env/hashtable.c
index 339cc19..70102f9 100644
--- a/test/env/hashtable.c
+++ b/test/env/hashtable.c
@@ -80,7 +80,7 @@
 		ut_asserteq_str(key, ritem->key);
 		ut_asserteq_str(key, ritem->data);
 
-		ut_asserteq(1, hdelete_r(key, htab, 0));
+		ut_asserteq(0, hdelete_r(key, htab, 0));
 	}
 
 	return 0;