diff --git a/README b/README
index 91c5a1a8fa3..263e31ab7f6 100644
--- a/README
+++ b/README
@@ -3229,6 +3229,22 @@ List of environment variables (most likely not complete):
 
   bootargs	- Boot arguments when booting an RTOS image
 
+  bootargs_subst - Substitution parameters for bootargs. These are applied after
+		  the commandline has been built. The format is:
+
+		    <key>=<value>[!<key>=<value>...]
+
+		  where
+		    <key> is a string to replace
+		    <value> is the value to replace it with (either a simple
+		       string or an environment variable starting with $
+
+		  One use for this is to insert the root-disk UUID into the
+		  command line where bootargs contains "root=%U"
+
+		    part uuid mmc 2:2 uuid
+		    setenv cmdline_repl %U=$uuid
+
   bootfile	- Name of the image to load with TFTP
 
   bootm_low	- Memory range available for image processing in the bootm
diff --git a/arch/Kconfig b/arch/Kconfig
index 6caf2338bcf..421ea9a9b51 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -141,6 +141,7 @@ config SANDBOX
 	imply CMD_PMC
 	imply CMD_CLONE
 	imply SILENT_CONSOLE
+	imply BOOTARGS_SUBST
 
 config SH
 	bool "SuperH architecture"
diff --git a/common/Kconfig.boot b/common/Kconfig.boot
index 4191bfb34df..c5668cf2931 100644
--- a/common/Kconfig.boot
+++ b/common/Kconfig.boot
@@ -848,6 +848,26 @@ config BOOTARGS
 	  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. Set the
+	  'bootargs_subst' envvar to control this. The format is:
+
+		<key>=<value>[!<key>=<value>...]
+
+		  where
+		    <key> is a string to replace
+		    <value> is the value to replace it with (either a simple
+		       string or an environment variable starting with $
+
+		  One use for this is to insert the root-disk UUID into the
+		  command line where bootargs contains "root=%U"
+
+		    part uuid mmc 2:2 uuid
+		    setenv cmdline_repl %U=$uuid
+
 config USE_BOOTCOMMAND
 	bool "Enable a default value for bootcmd"
 	help
diff --git a/common/bootm.c b/common/bootm.c
index 5484c2be900..0a89a72a660 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -540,6 +540,68 @@ static int fixup_silent_linux(char *buf, int maxlen)
 	return 0;
 }
 
+/**
+ * process_subst() - Handle substitution of %x fields in the environment
+ *
+ * For every "%x" found, it is replaced with the value of envvar "bootargs_x"
+ * where x is a case-sensitive alphanumeric character. If that environment
+ * variable does not exist, no substitution is made and the %x remains, to
+ * allow for plain % characters in the string.
+ *
+ * @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)
+{
+	const char *in;
+	char *cmdline, *out;
+	bool percent;
+	int size;
+
+	/* Move to end of buffer */
+	size = strlen(buf) + 1;
+	cmdline = buf + maxlen - size;
+	if (buf + size > cmdline)
+		return -ENOSPC;
+	memmove(cmdline, buf, size);
+
+	/* Replace %c with value of envvar 'bootargs_%c' */
+	percent = false;
+	for (in = cmdline, out = buf; *in; in++) {
+		/* If we catch up with the input string, we're out of space */
+		if (out >= in)
+			return -ENOSPC;
+		if (percent) {
+			char var[12];
+			const char *val;
+
+			snprintf(var, sizeof(var), "bootargs_%c", *in);
+			val = env_get(var);
+			if (val) {
+				for (; *val; val++) {
+					if (out >= in)
+						return -ENOSPC;
+					*out++ = *val;
+				}
+				percent = false;
+				continue;
+			} else {
+				/* No value, put the % back */
+				*out++ = '%';
+			}
+		} else if (*in == '%') {
+			percent = true;
+			continue;
+		}
+		*out++ = *in;
+		percent = false;
+	}
+	*out++ = '\0';
+
+	return 0;
+}
+
 int bootm_process_cmdline(char *buf, int maxlen, int flags)
 {
 	int ret;
@@ -552,6 +614,11 @@ int bootm_process_cmdline(char *buf, int maxlen, int flags)
 		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);
+	}
 
 	return 0;
 }
@@ -567,7 +634,7 @@ int bootm_process_cmdline_env(int flags)
 	/* 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)
+	if (!do_silent && !IS_ENABLED(CONFIG_BOOTARGS_SUBST))
 		return 0;
 
 	env = env_get("bootargs");
@@ -700,8 +767,7 @@ int do_bootm_states(struct cmd_tbl *cmdtp, int flag, int argc,
 	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)) {
-		ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX ?
-						BOOTM_CL_SILENT : 0);
+		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;
diff --git a/include/bootm.h b/include/bootm.h
index 8d95fb2a90a..7f88ec718b8 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -78,8 +78,9 @@ 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	= 1,		/* All substitutions */
+	BOOTM_CL_ALL	= 3,		/* All substitutions */
 };
 
 /**
@@ -95,7 +96,10 @@ 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)
+ * 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
@@ -111,8 +115,10 @@ 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)
+ * 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
diff --git a/test/bootm.c b/test/bootm.c
index d0b29441d65..8a83be9719f 100644
--- a/test/bootm.c
+++ b/test/bootm.c
@@ -116,22 +116,122 @@ static int bootm_test_silent(struct unit_test_state *uts)
 }
 BOOTM_TEST(bootm_test_silent, 0);
 
+/* Test substitution processing */
+static int bootm_test_subst(struct unit_test_state *uts)
+{
+	char buf[BUF_SIZE];
+
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("some%Athing", buf);
+
+	/* Replace with shorter string */
+	ut_assertok(env_set("bootargs_A", "a"));
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someathing", buf);
+
+	/* Replace with same-length string */
+	ut_assertok(env_set("bootargs_A", "ab"));
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabthing", buf);
+
+	/* Replace with longer string */
+	ut_assertok(env_set("bootargs_A", "abc"));
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing", buf);
+
+	/* Check it is case sensitive */
+	strcpy(buf, "some%athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("some%athing", buf);
+
+	/* Check too long - need 12 bytes for each string */
+	strcpy(buf, "some%Athing");
+	ut_asserteq(-ENOSPC,
+		    bootm_process_cmdline(buf, 12 * 2 - 1, BOOTM_CL_SUBST));
+
+	/* Check just enough space */
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, 12 * 2, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing", buf);
+
+	/*
+	 * Check the substition string being too long. This results in a string
+	 * of 12 (13 bytes). Allow one more byte margin.
+	 */
+	ut_assertok(env_set("bootargs_A", "1234567890"));
+	strcpy(buf, "a%Ac");
+	ut_asserteq(-ENOSPC,
+		    bootm_process_cmdline(buf, 13, BOOTM_CL_SUBST));
+
+	strcpy(buf, "a%Ac");
+	ut_asserteq(0, bootm_process_cmdline(buf, 14, BOOTM_CL_SUBST));
+
+	/* Check multiple substitutions */
+	ut_assertok(env_set("bootargs_A", "abc"));
+	strcpy(buf, "some%Athing%Belse");
+	ut_asserteq(0, bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing%Belse", buf);
+
+	/* Check multiple substitutions */
+	ut_assertok(env_set("bootargs_B", "123"));
+	strcpy(buf, "some%Athing%Belse");
+	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);
-	env_set("silent_linux", NULL);
-	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SUBST));
 	ut_assertnull(env_get("bootargs"));
 
-	env_set("bootargs", CONSOLE_STR);
-	env_set("silent_linux", "yes");
+	ut_assertok(env_set("bootargs", "some%Athing"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SUBST));
+	ut_asserteq_str("some%Athing", 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("bootargs", "some%Athing"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
+	ut_asserteq_str("some%Athing console=", env_get("bootargs"));
+
 	return 0;
 }
-BOOTM_TEST(bootm_test_silent_var, 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%Athing " CONSOLE_STR));
+	ut_assertok(env_set("bootargs_A", "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[])
 {
