diff --git a/common/Kconfig b/common/Kconfig
index 620d41f9ea63ba15463e95aedbbb0c5979e6d395..ccf5475bac63ae4b2e2ff54ed1afe4b258f996b3 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -679,3 +679,31 @@ config CMD_TPM_TEST
 endmenu
 
 endmenu
+
+config CONSOLE_RECORD
+	bool "Console recording"
+	help
+	  This provides a way to record console output (and provide console
+	  input) through cirular buffers. This is mostly useful for testing.
+	  Console output is recorded even when the console is silent.
+	  To enable console recording, call console_record_reset_enable()
+	  from your code.
+
+config CONSOLE_RECORD_OUT_SIZE
+	hex "Output buffer size"
+	depends on CONSOLE_RECORD
+	default 0x400 if CONSOLE_RECORD
+	help
+	  Set the size of the console output buffer. When this fills up, no
+	  more data will be recorded until some is removed. The buffer is
+	  allocated immediately after the malloc() region is ready.
+
+config CONSOLE_RECORD_IN_SIZE
+	hex "Input buffer size"
+	depends on CONSOLE_RECORD
+	default 0x100 if CONSOLE_RECORD
+	help
+	  Set the size of the console input buffer. When this contains data,
+	  tstc() and getc() will use this in preference to real device input.
+	  The buffer is allocated immediately after the malloc() region is
+	  ready.
diff --git a/common/board_f.c b/common/board_f.c
index 09baa5c5504546b7197666e1f94796b8f2c1d76b..b035c90ff3b7a7fd05c1e4b1f9d832b7faf8dba3 100644
--- a/common/board_f.c
+++ b/common/board_f.c
@@ -737,6 +737,15 @@ static int mark_bootstage(void)
 	return 0;
 }
 
+static int initf_console_record(void)
+{
+#if defined(CONFIG_CONSOLE_RECORD) && defined(CONFIG_SYS_MALLOC_F_LEN)
+	return console_record_init();
+#else
+	return 0;
+#endif
+}
+
 static int initf_dm(void)
 {
 #if defined(CONFIG_DM) && defined(CONFIG_SYS_MALLOC_F_LEN)
@@ -773,6 +782,7 @@ static init_fnc_t init_sequence_f[] = {
 	trace_early_init,
 #endif
 	initf_malloc,
+	initf_console_record,
 #if defined(CONFIG_MPC85xx) || defined(CONFIG_MPC86xx)
 	/* TODO: can this go into arch_cpu_init()? */
 	probecpu,
diff --git a/common/board_r.c b/common/board_r.c
index 85aef95a89589c12c634ebeb420e691a073b6699..f7118e8fc486e74c6218762d6902fe07a2cbf413 100644
--- a/common/board_r.c
+++ b/common/board_r.c
@@ -280,6 +280,15 @@ static int initr_malloc(void)
 	return 0;
 }
 
+static int initr_console_record(void)
+{
+#if defined(CONFIG_CONSOLE_RECORD)
+	return console_record_init();
+#else
+	return 0;
+#endif
+}
+
 #ifdef CONFIG_SYS_NONCACHED_MEMORY
 static int initr_noncached(void)
 {
@@ -731,6 +740,7 @@ init_fnc_t init_sequence_r[] = {
 #endif
 	initr_barrier,
 	initr_malloc,
+	initr_console_record,
 #ifdef CONFIG_SYS_NONCACHED_MEMORY
 	initr_noncached,
 #endif
diff --git a/common/console.c b/common/console.c
index 10972b04a6f6da4980f1ceaea93d1b8f37d0a38a..b3f126cceba6633ef9e49598a81a3a1f54908d29 100644
--- a/common/console.c
+++ b/common/console.c
@@ -378,6 +378,15 @@ int getc(void)
 	if (!gd->have_console)
 		return 0;
 
+#ifdef CONFIG_CONSOLE_RECORD
+	if (gd->console_in.start) {
+		int ch;
+
+		ch = membuff_getbyte(&gd->console_in);
+		if (ch != -1)
+			return 1;
+	}
+#endif
 	if (gd->flags & GD_FLG_DEVINIT) {
 		/* Get from the standard input */
 		return fgetc(stdin);
@@ -396,7 +405,12 @@ int tstc(void)
 
 	if (!gd->have_console)
 		return 0;
-
+#ifdef CONFIG_CONSOLE_RECORD
+	if (gd->console_in.start) {
+		if (membuff_peekbyte(&gd->console_in) != -1)
+			return 1;
+	}
+#endif
 	if (gd->flags & GD_FLG_DEVINIT) {
 		/* Test the standard input */
 		return ftstc(stdin);
@@ -470,6 +484,10 @@ void putc(const char c)
 		return;
 	}
 #endif
+#ifdef CONFIG_CONSOLE_RECORD
+	if (gd && (gd->flags & GD_FLG_RECORD) && gd->console_out.start)
+		membuff_putbyte(&gd->console_out, c);
+#endif
 #ifdef CONFIG_SILENT_CONSOLE
 	if (gd->flags & GD_FLG_SILENT)
 		return;
@@ -513,6 +531,10 @@ void puts(const char *s)
 		return;
 	}
 #endif
+#ifdef CONFIG_CONSOLE_RECORD
+	if (gd && (gd->flags & GD_FLG_RECORD) && gd->console_out.start)
+		membuff_put(&gd->console_out, s, strlen(s));
+#endif
 #ifdef CONFIG_SILENT_CONSOLE
 	if (gd->flags & GD_FLG_SILENT)
 		return;
@@ -575,6 +597,32 @@ int vprintf(const char *fmt, va_list args)
 	return i;
 }
 
+#ifdef CONFIG_CONSOLE_RECORD
+int console_record_init(void)
+{
+	int ret;
+
+	ret = membuff_new(&gd->console_out, CONFIG_CONSOLE_RECORD_OUT_SIZE);
+	if (ret)
+		return ret;
+	ret = membuff_new(&gd->console_in, CONFIG_CONSOLE_RECORD_IN_SIZE);
+
+	return ret;
+}
+
+void console_record_reset(void)
+{
+	membuff_purge(&gd->console_out);
+	membuff_purge(&gd->console_in);
+}
+
+void console_record_reset_enable(void)
+{
+	console_record_reset();
+	gd->flags |= GD_FLG_RECORD;
+}
+#endif
+
 /* test if ctrl-c was pressed */
 static int ctrlc_disabled = 0;	/* see disable_ctrl() */
 static int ctrlc_was_pressed = 0;
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index d0383f3d7693bebbd8e757a5027e7c9fb26a721c..1abdcaa6b7cd3f9f21affd12e941a82b7b135bbf 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -21,6 +21,7 @@
  */
 
 #ifndef __ASSEMBLY__
+#include <membuff.h>
 #include <linux/list.h>
 
 typedef struct global_data {
@@ -103,6 +104,10 @@ typedef struct global_data {
 #endif
 	struct udevice *cur_serial_dev;	/* current serial device */
 	struct arch_global_data arch;	/* architecture-specific data */
+#ifdef CONFIG_CONSOLE_RECORD
+	struct membuff console_out;	/* console output */
+	struct membuff console_in;	/* console input */
+#endif
 } gd_t;
 #endif
 
@@ -121,5 +126,6 @@ typedef struct global_data {
 #define GD_FLG_FULL_MALLOC_INIT	0x00200	/* Full malloc() is ready	   */
 #define GD_FLG_SPL_INIT		0x00400	/* spl_init() has been called	   */
 #define GD_FLG_SKIP_RELOC	0x00800	/* Don't relocate */
+#define GD_FLG_RECORD		0x01000	/* Record console */
 
 #endif /* __ASM_GENERIC_GBL_DATA_H */
diff --git a/include/console.h b/include/console.h
index 097518d1503f9431f1cbf01d7d6d50188e5a5b68..3d37f6a53bf71f4698ca601ae9d2ffc8b624b8eb 100644
--- a/include/console.h
+++ b/include/console.h
@@ -20,6 +20,28 @@ void clear_ctrlc(void);	/* clear the Control-C condition */
 int disable_ctrlc(int);	/* 1 to disable, 0 to enable Control-C detect */
 int confirm_yesno(void);        /*  1 if input is "y", "Y", "yes" or "YES" */
 
+/**
+ * console_record_init() - set up the console recording buffers
+ *
+ * This should be called as soon as malloc() is available so that the maximum
+ * amount of console output can be recorded.
+ */
+int console_record_init(void);
+
+/**
+ * console_record_reset() - reset the console recording buffers
+ *
+ * Removes any data in the buffers
+ */
+void console_record_reset(void);
+
+/**
+ * console_record_reset_enable() - reset and enable the console buffers
+ *
+ * This should be called to enable the console buffer.
+ */
+void console_record_reset_enable(void);
+
 /*
  * CONSOLE multiplexing.
  */