Browse Source

Merge branch '2021-06-08-display-and-logging-updates' into next

To quote Simon, first for the display changes:
At present we have two ways of showing a hex dump. Once has been in
U-Boot since the dawn of time and the other was recently added from
Linux.

They both have their own unique features.

This series makes a few changes to bring them closer together. It also
adds support for logging a buffer, which is useful since it can put it
through the same log drivers as other logging output.

Also it adds tests, so we can check the behaviour.

And then the logging changes:
At present when logging is not enabled, all log() calls become nops.
This does not seem right, since if the log level is high enough then
there should be some sort of message. So in that case, this series
updates it to print the message if the log level is above LOGL_INFO.

This mimics the behaviour for the log_...() macros like log_debug() and
log_info(), so we can drop the special case for these.

Also the current implementation does not support multiple log calls on
the same line nicely. The tags are repeated so the line is very hard to
read.  This series adds that as a new feature.
Tom Rini 2 years ago
parent
commit
da29243251

+ 14 - 4
common/console.c

@@ -95,16 +95,22 @@ static void console_record_putc(const char c)
 {
 	if (!(gd->flags & GD_FLG_RECORD))
 		return;
-	if  (gd->console_out.start)
-		membuff_putbyte((struct membuff *)&gd->console_out, c);
+	if  (gd->console_out.start &&
+	     !membuff_putbyte((struct membuff *)&gd->console_out, c))
+		gd->flags |= GD_FLG_RECORD_OVF;
 }
 
 static void console_record_puts(const char *s)
 {
 	if (!(gd->flags & GD_FLG_RECORD))
 		return;
-	if  (gd->console_out.start)
-		membuff_put((struct membuff *)&gd->console_out, s, strlen(s));
+	if  (gd->console_out.start) {
+		int len = strlen(s);
+
+		if (membuff_put((struct membuff *)&gd->console_out, s, len) !=
+		    len)
+			gd->flags |= GD_FLG_RECORD_OVF;
+	}
 }
 
 static int console_record_getc(void)
@@ -742,6 +748,7 @@ void console_record_reset(void)
 {
 	membuff_purge((struct membuff *)&gd->console_out);
 	membuff_purge((struct membuff *)&gd->console_in);
+	gd->flags &= ~GD_FLG_RECORD_OVF;
 }
 
 int console_record_reset_enable(void)
@@ -754,6 +761,9 @@ int console_record_reset_enable(void)
 
 int console_record_readline(char *str, int maxlen)
 {
+	if (gd->flags & GD_FLG_RECORD_OVF)
+		return -ENOSPC;
+
 	return membuff_readline((struct membuff *)&gd->console_out, str,
 				maxlen, ' ');
 }

+ 30 - 0
common/log.c

@@ -284,6 +284,36 @@ int _log(enum log_category_t cat, enum log_level_t level, const char *file,
 	return 0;
 }
 
+#define MAX_LINE_LENGTH_BYTES		64
+#define DEFAULT_LINE_LENGTH_BYTES	16
+
+int _log_buffer(enum log_category_t cat, enum log_level_t level,
+		const char *file, int line, const char *func, ulong addr,
+		const void *data, uint width, uint count, uint linelen)
+{
+	if (linelen * width > MAX_LINE_LENGTH_BYTES)
+		linelen = MAX_LINE_LENGTH_BYTES / width;
+	if (linelen < 1)
+		linelen = DEFAULT_LINE_LENGTH_BYTES / width;
+
+	while (count) {
+		uint thislinelen;
+		char buf[HEXDUMP_MAX_BUF_LENGTH(width * linelen)];
+
+		thislinelen = hexdump_line(addr, data, width, count, linelen,
+					   buf, sizeof(buf));
+		assert(thislinelen >= 0);
+		_log(cat, level, file, line, func, "%s\n", buf);
+
+		/* update references */
+		data += thislinelen * width;
+		addr += thislinelen * width;
+		count -= thislinelen;
+	}
+
+	return 0;
+}
+
 int log_add_filter_flags(const char *drv_name, enum log_category_t cat_list[],
 			 enum log_level_t level, const char *file_list,
 			 int flags)

+ 1 - 0
configs/sandbox_defconfig

@@ -21,6 +21,7 @@ CONFIG_BOOTSTAGE_STASH_SIZE=0x4096
 CONFIG_CONSOLE_RECORD=y
 CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000
 CONFIG_PRE_CONSOLE_BUFFER=y
+CONFIG_LOG=y
 CONFIG_DISPLAY_BOARDINFO_LATE=y
 CONFIG_MISC_INIT_F=y
 CONFIG_STACKPROTECTOR=y

+ 4 - 2
doc/develop/logging.rst

@@ -52,6 +52,10 @@ If CONFIG_LOG is not set, then no logging will be available.
 The above have SPL and TPL versions also, e.g. CONFIG_SPL_LOG_MAX_LEVEL and
 CONFIG_TPL_LOG_MAX_LEVEL.
 
+If logging is disabled, the default behaviour is to output any message at
+level LOGL_INFO and below. If logging is disabled and DEBUG is defined (at
+the very top of a C file) then any message at LOGL_DEBUG will be written.
+
 Temporary logging within a single file
 --------------------------------------
 
@@ -291,8 +295,6 @@ More logging destinations:
 
 Convert debug() statements in the code to log() statements
 
-Support making printf() emit log statements at L_INFO level
-
 Convert error() statements in the code to log() statements
 
 Figure out what to do with BUG(), BUG_ON() and warn_non_spl()

+ 10 - 6
include/asm-generic/global_data.h

@@ -571,30 +571,34 @@ enum gd_flags {
 	 * @GD_FLG_RECORD: record console
 	 */
 	GD_FLG_RECORD = 0x01000,
+	/**
+	 * @GD_FLG_RECORD_OVF: record console overflow
+	 */
+	GD_FLG_RECORD_OVF = 0x02000,
 	/**
 	 * @GD_FLG_ENV_DEFAULT: default variable flag
 	 */
-	GD_FLG_ENV_DEFAULT = 0x02000,
+	GD_FLG_ENV_DEFAULT = 0x04000,
 	/**
 	 * @GD_FLG_SPL_EARLY_INIT: early SPL initialization is done
 	 */
-	GD_FLG_SPL_EARLY_INIT = 0x04000,
+	GD_FLG_SPL_EARLY_INIT = 0x08000,
 	/**
 	 * @GD_FLG_LOG_READY: log system is ready for use
 	 */
-	GD_FLG_LOG_READY = 0x08000,
+	GD_FLG_LOG_READY = 0x10000,
 	/**
 	 * @GD_FLG_WDT_READY: watchdog is ready for use
 	 */
-	GD_FLG_WDT_READY = 0x10000,
+	GD_FLG_WDT_READY = 0x20000,
 	/**
 	 * @GD_FLG_SKIP_LL_INIT: don't perform low-level initialization
 	 */
-	GD_FLG_SKIP_LL_INIT = 0x20000,
+	GD_FLG_SKIP_LL_INIT = 0x40000,
 	/**
 	 * @GD_FLG_SMP_READY: SMP initialization is complete
 	 */
-	GD_FLG_SMP_READY = 0x40000,
+	GD_FLG_SMP_READY = 0x80000,
 };
 
 #endif /* __ASSEMBLY__ */

+ 2 - 1
include/console.h

@@ -72,7 +72,8 @@ int console_record_reset_enable(void);
  *
  * @str: Place to put string
  * @maxlen: Maximum length of @str including nul terminator
- * @return length of string returned
+ * @return length of string returned, or -ENOSPC if the console buffer was
+ *	overflowed by the output
  */
 int console_record_readline(char *str, int maxlen);
 

+ 25 - 0
include/display_options.h

@@ -47,6 +47,31 @@ void print_freq(uint64_t freq, const char *suffix);
 int print_buffer(ulong addr, const void *data, uint width, uint count,
 		 uint linelen);
 
+/*
+ * Maximum length of an output line is when width == 1
+ *	9 for address,
+ *	a space, two hex digits and an ASCII character for each byte
+ *	2 spaces between the hex and ASCII
+ *	\0 terminator
+ */
+#define HEXDUMP_MAX_BUF_LENGTH(bytes)	(9 + (bytes) * 4 + 3)
+
+/**
+ * hexdump_line() - Print out a single line of a hex dump
+ *
+ * @addr:	Starting address to display at start of line
+ * @data:	pointer to data buffer
+ * @width:	data value width.  May be 1, 2, or 4.
+ * @count:	number of values to display
+ * @linelen:	Number of values to print per line; specify 0 for default length
+ * @out:	Output buffer to hold the dump
+ * @size:	Size of output buffer in bytes
+ * @return number of bytes processed, if OK, -ENOSPC if buffer too small
+ *
+ */
+int hexdump_line(ulong addr, const void *data, uint width, uint count,
+		 uint linelen, char *out, int size);
+
 /**
  * display_options() - display the version string / build tag
  *

+ 78 - 3
include/hexdump.h

@@ -10,7 +10,7 @@
 #include <linux/ctype.h>
 #include <linux/types.h>
 
-enum {
+enum dump_prefix_t {
 	DUMP_PREFIX_NONE,
 	DUMP_PREFIX_ADDRESS,
 	DUMP_PREFIX_OFFSET
@@ -81,10 +81,85 @@ static inline char *bin2hex(char *dst, const void *src, size_t count)
 	return dst;
 }
 
+/**
+ * hex_dump_to_buffer - convert a blob of data to "hex ASCII" in memory
+ * @buf: data blob to dump
+ * @len: number of bytes in the @buf
+ * @rowsize: number of bytes to print per line; max 64
+ * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
+ * @linebuf: where to put the converted data
+ * @linebuflen: total size of @linebuf, including space for terminating NUL
+ * @ascii: include ASCII after the hex output
+ *
+ * hex_dump_to_buffer() works on one "line" of output at a time, i.e.,
+ * 16 or 32 bytes of input data converted to hex + ASCII output.
+ *
+ * Given a buffer of u8 data, hex_dump_to_buffer() converts the input data
+ * to a hex + ASCII dump at the supplied memory location.
+ * The converted output is always NUL-terminated.
+ *
+ * E.g.:
+ *   hex_dump_to_buffer(frame->data, frame->len, 16, 1,
+ *			linebuf, sizeof(linebuf), true);
+ *
+ * example output buffer:
+ * 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
+ *
+ * Return:
+ * The amount of bytes placed in the buffer without terminating NUL. If the
+ * output was truncated, then the return value is the number of bytes
+ * (excluding the terminating NUL) which would have been written to the final
+ * string if enough space had been available.
+ */
 int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize,
 		       char *linebuf, size_t linebuflen, bool ascii);
-void print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
-		    int groupsize, const void *buf, size_t len, bool ascii);
+
+/**
+ * print_hex_dump - print a text hex dump to syslog for a binary blob of data
+ * @prefix_str: string to prefix each line with;
+ *  caller supplies trailing spaces for alignment if desired
+ * @prefix_type: controls whether prefix of an offset, address, or none
+ *  is printed (see enum dump_prefix_t)
+ * @rowsize: number of bytes to print per line; max 64
+ * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
+ * @buf: data blob to dump
+ * @len: number of bytes in the @buf
+ * @ascii: include ASCII after the hex output
+ * Returns: 0 if finished normally, -EINTR if Ctrl-C was pressed, -ENOSYS if not
+ * supported
+ *
+ * Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump
+ * to the stdio, with an optional leading prefix.
+ *
+ * print_hex_dump() works on one "line" of output at a time, i.e.,
+ * 16 or 32 bytes of input data converted to hex + ASCII output.
+ * print_hex_dump() iterates over the entire input @buf, breaking it into
+ * "line size" chunks to format and print.
+ *
+ * E.g.:
+ *   print_hex_dump("raw data: ", DUMP_PREFIX_ADDRESS, 16, 1, frame->data,
+ *                  frame->len, true);
+ *
+ * Example output using %DUMP_PREFIX_OFFSET and 1-byte mode:
+ * 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
+ * Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode:
+ * ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.
+ */
+int print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
+		   int groupsize, const void *buf, size_t len, bool ascii);
+
+/**
+ * print_hex_dump_bytes - shorthand form of print_hex_dump() with default params
+ * @prefix_str: string to prefix each line with;
+ *  caller supplies trailing spaces for alignment if desired
+ * @prefix_type: controls whether prefix of an offset, address, or none
+ *  is printed (see enum dump_prefix_t)
+ * @buf: data blob to dump
+ * @len: number of bytes in the @buf
+ *
+ * Calls print_hex_dump(), rowsize of 16, groupsize of 1,
+ * and ASCII output included.
+ */
 void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
 			  const void *buf, size_t len);
 

+ 50 - 19
include/log.h

@@ -140,6 +140,24 @@ static inline int _log_nop(enum log_category_t cat, enum log_level_t level,
 	return 0;
 }
 
+/**
+ * _log_buffer - Internal function to print data buffer in hex and ascii form
+ *
+ * @cat: Category of log record (indicating which subsystem generated it)
+ * @level: Level of log record (indicating its severity)
+ * @file: File name of file where log record was generated
+ * @line: Line number in file where log record was generated
+ * @func: Function where log record was generated
+ * @addr:	Starting address to display at start of line
+ * @data:	pointer to data buffer
+ * @width:	data value width.  May be 1, 2, or 4.
+ * @count:	number of values to display
+ * @linelen:	Number of values to print per line; specify 0 for default length
+ */
+int _log_buffer(enum log_category_t cat, enum log_level_t level,
+		const char *file, int line, const char *func, ulong addr,
+		const void *data, uint width, uint count, uint linelen);
+
 /* Define this at the top of a file to add a prefix to debug messages */
 #ifndef pr_fmt
 #define pr_fmt(fmt) fmt
@@ -156,6 +174,10 @@ static inline int _log_nop(enum log_category_t cat, enum log_level_t level,
  */
 #if CONFIG_IS_ENABLED(LOG)
 #define _LOG_MAX_LEVEL CONFIG_VAL(LOG_MAX_LEVEL)
+#else
+#define _LOG_MAX_LEVEL LOGL_INFO
+#endif
+
 #define log_emer(_fmt...)	log(LOG_CATEGORY, LOGL_EMERG, ##_fmt)
 #define log_alert(_fmt...)	log(LOG_CATEGORY, LOGL_ALERT, ##_fmt)
 #define log_crit(_fmt...)	log(LOG_CATEGORY, LOGL_CRIT, ##_fmt)
@@ -167,41 +189,50 @@ static inline int _log_nop(enum log_category_t cat, enum log_level_t level,
 #define log_content(_fmt...)	log(LOG_CATEGORY, LOGL_DEBUG_CONTENT, ##_fmt)
 #define log_io(_fmt...)		log(LOG_CATEGORY, LOGL_DEBUG_IO, ##_fmt)
 #define log_cont(_fmt...)	log(LOGC_CONT, LOGL_CONT, ##_fmt)
-#else
-#define _LOG_MAX_LEVEL LOGL_INFO
-#define log_emerg(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_alert(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_crit(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_err(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_warning(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_notice(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_info(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_cont(_fmt, ...)	printf(_fmt, ##__VA_ARGS__)
-#define log_debug(_fmt, ...)	debug(_fmt, ##__VA_ARGS__)
-#define log_content(_fmt...)	log_nop(LOG_CATEGORY, \
-					LOGL_DEBUG_CONTENT, ##_fmt)
-#define log_io(_fmt...)		log_nop(LOG_CATEGORY, LOGL_DEBUG_IO, ##_fmt)
-#endif
 
-#if CONFIG_IS_ENABLED(LOG)
 #ifdef LOG_DEBUG
 #define _LOG_DEBUG	LOGL_FORCE_DEBUG
 #else
 #define _LOG_DEBUG	0
 #endif
 
+#if CONFIG_IS_ENABLED(LOG)
+
 /* Emit a log record if the level is less that the maximum */
 #define log(_cat, _level, _fmt, _args...) ({ \
 	int _l = _level; \
-	if (CONFIG_IS_ENABLED(LOG) && \
-	    (_LOG_DEBUG != 0 || _l <= _LOG_MAX_LEVEL)) \
+	if (_LOG_DEBUG != 0 || _l <= _LOG_MAX_LEVEL) \
 		_log((enum log_category_t)(_cat), \
 		     (enum log_level_t)(_l | _LOG_DEBUG), __FILE__, \
 		     __LINE__, __func__, \
 		      pr_fmt(_fmt), ##_args); \
 	})
+
+/* Emit a dump if the level is less that the maximum */
+#define log_buffer(_cat, _level, _addr, _data, _width, _count, _linelen)  ({ \
+	int _l = _level; \
+	if (_LOG_DEBUG != 0 || _l <= _LOG_MAX_LEVEL) \
+		_log_buffer((enum log_category_t)(_cat), \
+			    (enum log_level_t)(_l | _LOG_DEBUG), __FILE__, \
+			    __LINE__, __func__, _addr, _data, \
+			    _width, _count, _linelen); \
+	})
 #else
-#define log(_cat, _level, _fmt, _args...)
+
+/* Note: _LOG_DEBUG != 0 avoids a warning with clang */
+#define log(_cat, _level, _fmt, _args...) ({ \
+	int _l = _level; \
+	if (_LOG_DEBUG != 0 || _l <= LOGL_INFO || \
+	    (_DEBUG && _l == LOGL_DEBUG)) \
+		printf(_fmt, ##_args); \
+	})
+
+#define log_buffer(_cat, _level, _addr, _data, _width, _count, _linelen)  ({ \
+	int _l = _level; \
+	if (_LOG_DEBUG != 0 || _l <= LOGL_INFO || \
+	    (_DEBUG && _l == LOGL_DEBUG)) \
+		print_buffer(_addr, _data, _width, _count, _linelen); \
+	})
 #endif
 
 #define log_nop(_cat, _level, _fmt, _args...) ({ \

+ 1 - 0
include/test/suites.h

@@ -41,6 +41,7 @@ int do_ut_mem(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_optee(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_overlay(struct cmd_tbl *cmdtp, int flag, int argc,
 		  char *const argv[]);
+int do_ut_print(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_setexpr(struct cmd_tbl *cmdtp, int flag, int argc,
 		  char *const argv[]);
 int do_ut_str(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);

+ 2 - 2
include/test/test.h

@@ -32,8 +32,8 @@ struct unit_test_state {
 	struct udevice *testdev;
 	int force_fail_alloc;
 	int skip_post_probe;
-	char expect_str[256];
-	char actual_str[256];
+	char expect_str[512];
+	char actual_str[512];
 };
 
 /* Test flags for each test */

+ 70 - 45
lib/display_options.c

@@ -131,10 +131,11 @@ void print_size(uint64_t size, const char *s)
 	printf (" %ciB%s", c, s);
 }
 
-#define MAX_LINE_LENGTH_BYTES (64)
-#define DEFAULT_LINE_LENGTH_BYTES (16)
-int print_buffer(ulong addr, const void *data, uint width, uint count,
-		 uint linelen)
+#define MAX_LINE_LENGTH_BYTES		64
+#define DEFAULT_LINE_LENGTH_BYTES	16
+
+int hexdump_line(ulong addr, const void *data, uint width, uint count,
+		 uint linelen, char *out, int size)
 {
 	/* linebuf as a union causes proper alignment */
 	union linebuf {
@@ -143,62 +144,86 @@ int print_buffer(ulong addr, const void *data, uint width, uint count,
 		uint16_t us[MAX_LINE_LENGTH_BYTES/sizeof(uint16_t) + 1];
 		uint8_t  uc[MAX_LINE_LENGTH_BYTES/sizeof(uint8_t) + 1];
 	} lb;
+	uint thislinelen;
 	int i;
 	ulong x;
 
+	if (linelen * width > MAX_LINE_LENGTH_BYTES)
+		linelen = MAX_LINE_LENGTH_BYTES / width;
+	if (linelen < 1)
+		linelen = DEFAULT_LINE_LENGTH_BYTES / width;
+
+	/*
+	 * Check the size here so that we don't need to use snprintf(). This
+	 * helps to reduce code size
+	 */
+	if (size < HEXDUMP_MAX_BUF_LENGTH(linelen * width))
+		return -ENOSPC;
+
+	thislinelen = linelen;
+	out += sprintf(out, "%08lx:", addr);
+
+	/* check for overflow condition */
+	if (count < thislinelen)
+		thislinelen = count;
+
+	/* Copy from memory into linebuf and print hex values */
+	for (i = 0; i < thislinelen; i++) {
+		if (width == 4)
+			x = lb.ui[i] = *(volatile uint32_t *)data;
+		else if (MEM_SUPPORT_64BIT_DATA && width == 8)
+			x = lb.uq[i] = *(volatile ulong *)data;
+		else if (width == 2)
+			x = lb.us[i] = *(volatile uint16_t *)data;
+		else
+			x = lb.uc[i] = *(volatile uint8_t *)data;
+		if (CONFIG_IS_ENABLED(USE_TINY_PRINTF))
+			out += sprintf(out, " %x", (uint)x);
+		else
+			out += sprintf(out, " %0*lx", width * 2, x);
+		data += width;
+	}
+
+	/* fill line with whitespace for nice ASCII print */
+	for (i = 0; i < (linelen - thislinelen) * (width * 2 + 1); i++)
+		*out++ = ' ';
+
+	/* Print data in ASCII characters */
+	for (i = 0; i < thislinelen * width; i++) {
+		if (!isprint(lb.uc[i]) || lb.uc[i] >= 0x80)
+			lb.uc[i] = '.';
+	}
+	lb.uc[i] = '\0';
+	out += sprintf(out, "  %s", lb.uc);
+
+	return thislinelen;
+}
+
+int print_buffer(ulong addr, const void *data, uint width, uint count,
+		 uint linelen)
+{
 	if (linelen*width > MAX_LINE_LENGTH_BYTES)
 		linelen = MAX_LINE_LENGTH_BYTES / width;
 	if (linelen < 1)
 		linelen = DEFAULT_LINE_LENGTH_BYTES / width;
 
 	while (count) {
-		uint thislinelen = linelen;
-		printf("%08lx:", addr);
-
-		/* check for overflow condition */
-		if (count < thislinelen)
-			thislinelen = count;
-
-		/* Copy from memory into linebuf and print hex values */
-		for (i = 0; i < thislinelen; i++) {
-			if (width == 4)
-				x = lb.ui[i] = *(volatile uint32_t *)data;
-			else if (MEM_SUPPORT_64BIT_DATA && width == 8)
-				x = lb.uq[i] = *(volatile ulong *)data;
-			else if (width == 2)
-				x = lb.us[i] = *(volatile uint16_t *)data;
-			else
-				x = lb.uc[i] = *(volatile uint8_t *)data;
-			if (CONFIG_IS_ENABLED(USE_TINY_PRINTF))
-				printf(" %x", (uint)x);
-			else
-				printf(" %0*lx", width * 2, x);
-			data += width;
-		}
+		uint thislinelen;
+		char buf[HEXDUMP_MAX_BUF_LENGTH(width * linelen)];
 
-		while (thislinelen < linelen) {
-			/* fill line with whitespace for nice ASCII print */
-			for (i=0; i<width*2+1; i++)
-				puts(" ");
-			linelen--;
-		}
-
-		/* Print data in ASCII characters */
-		for (i = 0; i < thislinelen * width; i++) {
-			if (!isprint(lb.uc[i]) || lb.uc[i] >= 0x80)
-				lb.uc[i] = '.';
-		}
-		lb.uc[i] = '\0';
-		printf("    %s\n", lb.uc);
+		thislinelen = hexdump_line(addr, data, width, count, linelen,
+					   buf, sizeof(buf));
+		assert(thislinelen >= 0);
+		puts(buf);
+		putc('\n');
 
 		/* update references */
+		data += thislinelen * width;
 		addr += thislinelen * width;
 		count -= thislinelen;
 
-#ifndef CONFIG_SPL_BUILD
-		if (ctrlc())
-			return -1;
-#endif
+		if (!IS_ENABLED(CONFIG_SPL_BUILD) && ctrlc())
+			return -EINTR;
 	}
 
 	return 0;

+ 23 - 81
lib/hexdump.c

@@ -10,45 +10,18 @@
 
 #include <common.h>
 #include <hexdump.h>
+#include <mapmem.h>
 #include <linux/ctype.h>
 #include <linux/compat.h>
 #include <linux/log2.h>
 #include <asm/unaligned.h>
 
+#define MAX_LINE_LENGTH_BYTES	64
+
 const char hex_asc[] = "0123456789abcdef";
 const char hex_asc_upper[] = "0123456789ABCDEF";
 
 #if CONFIG_IS_ENABLED(HEXDUMP)
-/**
- * hex_dump_to_buffer - convert a blob of data to "hex ASCII" in memory
- * @buf: data blob to dump
- * @len: number of bytes in the @buf
- * @rowsize: number of bytes to print per line; must be 16 or 32
- * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
- * @linebuf: where to put the converted data
- * @linebuflen: total size of @linebuf, including space for terminating NUL
- * @ascii: include ASCII after the hex output
- *
- * hex_dump_to_buffer() works on one "line" of output at a time, i.e.,
- * 16 or 32 bytes of input data converted to hex + ASCII output.
- *
- * Given a buffer of u8 data, hex_dump_to_buffer() converts the input data
- * to a hex + ASCII dump at the supplied memory location.
- * The converted output is always NUL-terminated.
- *
- * E.g.:
- *   hex_dump_to_buffer(frame->data, frame->len, 16, 1,
- *			linebuf, sizeof(linebuf), true);
- *
- * example output buffer:
- * 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
- *
- * Return:
- * The amount of bytes placed in the buffer without terminating NUL. If the
- * output was truncated, then the return value is the number of bytes
- * (excluding the terminating NUL) which would have been written to the final
- * string if enough space had been available.
- */
 int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize,
 		       char *linebuf, size_t linebuflen, bool ascii)
 {
@@ -59,8 +32,10 @@ int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize,
 	int ascii_column;
 	int ret;
 
-	if (rowsize != 16 && rowsize != 32)
+	if (!rowsize)
 		rowsize = 16;
+	else
+		rowsize = min(rowsize, MAX_LINE_LENGTH_BYTES);
 
 	if (len > rowsize)		/* limit to one line at a time */
 		len = rowsize;
@@ -150,44 +125,17 @@ overflow1:
 	return ascii ? ascii_column + len : (groupsize * 2 + 1) * ngroups - 1;
 }
 
-/**
- * print_hex_dump - print a text hex dump to syslog for a binary blob of data
- * @prefix_str: string to prefix each line with;
- *  caller supplies trailing spaces for alignment if desired
- * @prefix_type: controls whether prefix of an offset, address, or none
- *  is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
- * @rowsize: number of bytes to print per line; must be 16 or 32
- * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
- * @buf: data blob to dump
- * @len: number of bytes in the @buf
- * @ascii: include ASCII after the hex output
- *
- * Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump
- * to the stdio, with an optional leading prefix.
- *
- * print_hex_dump() works on one "line" of output at a time, i.e.,
- * 16 or 32 bytes of input data converted to hex + ASCII output.
- * print_hex_dump() iterates over the entire input @buf, breaking it into
- * "line size" chunks to format and print.
- *
- * E.g.:
- *   print_hex_dump("raw data: ", DUMP_PREFIX_ADDRESS, 16, 1, frame->data,
- *                  frame->len, true);
- *
- * Example output using %DUMP_PREFIX_OFFSET and 1-byte mode:
- * 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
- * Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode:
- * ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.
- */
-void print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
-		    int groupsize, const void *buf, size_t len, bool ascii)
+int print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
+		   int groupsize, const void *buf, size_t len, bool ascii)
 {
 	const u8 *ptr = buf;
 	int i, linelen, remaining = len;
-	char linebuf[32 * 3 + 2 + 32 + 1];
+	char linebuf[MAX_LINE_LENGTH_BYTES * 3 + 2 + MAX_LINE_LENGTH_BYTES + 1];
 
-	if (rowsize != 16 && rowsize != 32)
+	if (!rowsize)
 		rowsize = 16;
+	else
+		rowsize = min(rowsize, MAX_LINE_LENGTH_BYTES);
 
 	for (i = 0; i < len; i += rowsize) {
 		linelen = min(remaining, rowsize);
@@ -198,7 +146,9 @@ void print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
 
 		switch (prefix_type) {
 		case DUMP_PREFIX_ADDRESS:
-			printf("%s%p: %s\n", prefix_str, ptr + i, linebuf);
+			printf("%s%0*lx: %s\n", prefix_str,
+			       IS_ENABLED(CONFIG_PHYS_64BIT) ? 16 : 8,
+			       (ulong)map_to_sysmem(ptr) + i, linebuf);
 			break;
 		case DUMP_PREFIX_OFFSET:
 			printf("%s%.8x: %s\n", prefix_str, i, linebuf);
@@ -207,21 +157,13 @@ void print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
 			printf("%s%s\n", prefix_str, linebuf);
 			break;
 		}
+		if (!IS_ENABLED(CONFIG_SPL_BUILD) && ctrlc())
+			return -EINTR;
 	}
+
+	return 0;
 }
 
-/**
- * print_hex_dump_bytes - shorthand form of print_hex_dump() with default params
- * @prefix_str: string to prefix each line with;
- *  caller supplies trailing spaces for alignment if desired
- * @prefix_type: controls whether prefix of an offset, address, or none
- *  is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
- * @buf: data blob to dump
- * @len: number of bytes in the @buf
- *
- * Calls print_hex_dump(), rowsize of 16, groupsize of 1,
- * and ASCII output included.
- */
 void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
 			  const void *buf, size_t len)
 {
@@ -232,14 +174,14 @@ void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
  * Some code in U-Boot copy-pasted from Linux kernel uses both
  * functions below so to keep stuff compilable we keep these stubs here.
  */
-void print_hex_dump(const char *prefix_str, int prefix_type,
-				  int rowsize, int groupsize, const void *buf,
-				  size_t len, bool ascii)
+int print_hex_dump(const char *prefix_str, int prefix_type, int rowsize,
+		   int groupsize, const void *buf, size_t len, bool ascii)
 {
+	return -ENOSYS;
 }
 
 void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
-					const void *buf, size_t len)
+			  const void *buf, size_t len)
 {
 }
 #endif /* CONFIG_HEXDUMP */

+ 13 - 13
test/cmd/mem_search.c

@@ -30,9 +30,9 @@ static int mem_test_ms_b(struct unit_test_state *uts)
 	buf[0x100] = 0x12;
 	ut_assertok(console_record_reset_enable());
 	run_command("ms.b 1 ff 12", 0);
-	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
+	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................");
 	ut_assert_nextline("--");
-	ut_assert_nextline("000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12    ................");
+	ut_assert_nextline("000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12  ................");
 	ut_assert_nextline("2 matches");
 	ut_assert_console_end();
 
@@ -57,7 +57,7 @@ static int mem_test_ms_w(struct unit_test_state *uts)
 	buf[BUF_SIZE / 2] = 0x1234;
 	ut_assertok(console_record_reset_enable());
 	run_command("ms.w 0 80 1234", 0);
-	ut_assert_nextline("00000030: 0000 0000 1234 0000 0000 0000 0000 0000    ....4...........");
+	ut_assert_nextline("00000030: 0000 0000 1234 0000 0000 0000 0000 0000  ....4...........");
 	ut_assert_nextline("1 match");
 	ut_assert_console_end();
 
@@ -82,7 +82,7 @@ static int mem_test_ms_l(struct unit_test_state *uts)
 	buf[BUF_SIZE / 4] = 0x12345678;
 	ut_assertok(console_record_reset_enable());
 	run_command("ms 0 40 12345678", 0);
-	ut_assert_nextline("00000030: 00000000 00000000 12345678 00000000    ........xV4.....");
+	ut_assert_nextline("00000030: 00000000 00000000 12345678 00000000  ........xV4.....");
 	ut_assert_nextline("1 match");
 	ut_assert_console_end();
 
@@ -212,10 +212,10 @@ static int mem_test_ms_mult(struct unit_test_state *uts)
 	strcpy(buf + BUF_SIZE - strlen(str) + 1, str);
 	ut_assertok(console_record_reset_enable());
 	run_command("ms.b 0 100 68 65 6c 6c 6f", 0);
-	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65    ..............he");
-	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00    llo.............");
+	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65  ..............he");
+	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00  llo.............");
 	ut_assert_nextline("--");
-	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00    ...hello........");
+	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00  ...hello........");
 	ut_assert_nextline("2 matches");
 	ut_assert_console_end();
 	unmap_sysmem(buf);
@@ -242,12 +242,12 @@ static int mem_test_ms_s(struct unit_test_state *uts)
 	strcpy(buf + 0xa1, str2);
 	ut_assertok(console_record_reset_enable());
 	run_command("ms.s 0 100 hello", 0);
-	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65    ..............he");
-	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00    llo.............");
+	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65  ..............he");
+	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00  llo.............");
 	ut_assert_nextline("--");
-	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00    ...hello........");
+	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00  ...hello........");
 	ut_assert_nextline("--");
-	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00    .hellothere.....");
+	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00  .hellothere.....");
 	ut_assert_nextline("3 matches");
 	ut_assert_console_end();
 
@@ -257,7 +257,7 @@ static int mem_test_ms_s(struct unit_test_state *uts)
 
 	ut_assertok(console_record_reset_enable());
 	run_command("ms.s 0 100 hello there", 0);
-	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00    .hellothere.....");
+	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00  .hellothere.....");
 	ut_assert_nextline("1 match");
 	ut_assert_console_end();
 
@@ -284,7 +284,7 @@ static int mem_test_ms_limit(struct unit_test_state *uts)
 	buf[0x76] = 0x12;
 	ut_assertok(console_record_reset_enable());
 	run_command("ms.b -l2 1 ff 12", 0);
-	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
+	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................");
 	ut_assert_nextline("--");
 	ut_assert_nextlinen("00000060: 00 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00");
 	ut_assert_nextline("2 matches (repeat command to check for more)");

+ 2 - 0
test/cmd_ut.c

@@ -51,6 +51,7 @@ static struct cmd_tbl cmd_ut_sub[] = {
 	U_BOOT_CMD_MKENT(setexpr, CONFIG_SYS_MAXARGS, 1, do_ut_setexpr, "",
 			 ""),
 #endif
+	U_BOOT_CMD_MKENT(print, CONFIG_SYS_MAXARGS, 1, do_ut_print, "", ""),
 #ifdef CONFIG_UT_TIME
 	U_BOOT_CMD_MKENT(time, CONFIG_SYS_MAXARGS, 1, do_ut_time, "", ""),
 #endif
@@ -132,6 +133,7 @@ static char ut_help_text[] =
 #ifdef CONFIG_UT_OVERLAY
 	"ut overlay [test-name]\n"
 #endif
+	"ut print [test-name]  - test printing\n"
 	"ut setexpr [test-name] - test setexpr command\n"
 #ifdef CONFIG_SANDBOX
 	"ut str - Basic test of string functions\n"

+ 3 - 3
test/dm/rtc.c

@@ -204,7 +204,7 @@ static int dm_test_rtc_cmd_rw(struct unit_test_state *uts)
 	ut_assert_console_end();
 
 	run_command("rtc read 0x30 2", 0);
-	ut_assert_nextline("00000030: aa bb                                              ..");
+	ut_assert_nextline("00000030: aa bb                                            ..");
 	ut_assert_console_end();
 
 	run_command("rtc dev 1", 0);
@@ -215,7 +215,7 @@ static int dm_test_rtc_cmd_rw(struct unit_test_state *uts)
 	ut_assert_console_end();
 
 	run_command("rtc read 0x30 2", 0);
-	ut_assert_nextline("00000030: cc dd                                              ..");
+	ut_assert_nextline("00000030: cc dd                                            ..");
 	ut_assert_console_end();
 
 	/*
@@ -227,7 +227,7 @@ static int dm_test_rtc_cmd_rw(struct unit_test_state *uts)
 	ut_assert_console_end();
 
 	run_command("rtc read 0x30 2", 0);
-	ut_assert_nextline("00000030: aa bb                                              ..");
+	ut_assert_nextline("00000030: aa bb                                            ..");
 	ut_assert_console_end();
 
 	return 0;

+ 2 - 0
test/log/Makefile

@@ -17,8 +17,10 @@ endif
 ifdef CONFIG_LOG
 obj-y += pr_cont_test.o
 obj-$(CONFIG_CONSOLE_RECORD) += cont_test.o
+obj-y += pr_cont_test.o
 else
 obj-$(CONFIG_CONSOLE_RECORD) += nolog_test.o
+obj-$(CONFIG_CONSOLE_RECORD) += nolog_ndebug.o
 endif
 
 endif # CONFIG_UT_LOG

+ 27 - 0
test/log/log_test.c

@@ -429,3 +429,30 @@ int log_test_dropped(struct unit_test_state *uts)
 	return 0;
 }
 LOG_TEST_FLAGS(log_test_dropped, UT_TESTF_CONSOLE_REC);
+
+/* Check log_buffer() */
+int log_test_buffer(struct unit_test_state *uts)
+{
+	u8 *buf;
+	int i;
+
+	buf = malloc(0x20);
+	ut_assertnonnull(buf);
+	memset(buf, '\0', 0x20);
+	for (i = 0; i < 0x11; i++)
+		buf[i] = i * 0x11;
+
+	ut_assertok(console_record_reset_enable());
+	log_buffer(LOGC_BOOT, LOGL_INFO, 0, buf, 1, 0x12, 0);
+
+	/* This one should product no output due to the debug level */
+	log_buffer(LOGC_BOOT, LOGL_DEBUG, 0, buf, 1, 0x12, 0);
+
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 10 00                                            ..");
+	ut_assert_console_end();
+	free(buf);
+
+	return 0;
+}
+LOG_TEST_FLAGS(log_test_buffer, UT_TESTF_CONSOLE_REC);

+ 39 - 0
test/log/nolog_ndebug.c

@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Logging function tests for CONFIG_LOG=n without #define DEBUG
+ */
+
+#include <common.h>
+#include <console.h>
+#include <log.h>
+#include <asm/global_data.h>
+#include <test/log.h>
+#include <test/ut.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define BUFFSIZE 32
+
+static int log_test_log_disabled_ndebug(struct unit_test_state *uts)
+{
+	char buf[BUFFSIZE];
+	int i;
+
+	memset(buf, 0, BUFFSIZE);
+	console_record_reset_enable();
+
+	/* Output a log record at every level */
+	for (i = LOGL_EMERG; i < LOGL_COUNT; i++)
+		log(LOGC_NONE, i, "testing level %i\n", i);
+	gd->flags &= ~GD_FLG_RECORD;
+
+	/* Since DEBUG is not defined, we expect to not get debug output */
+	for (i = LOGL_EMERG; i < LOGL_DEBUG; i++)
+		ut_assertok(ut_check_console_line(uts, "testing level %d", i));
+	ut_assertok(ut_check_console_end(uts));
+
+	return 0;
+}
+LOG_TEST(log_test_log_disabled_ndebug);

+ 3 - 0
test/log/nolog_test.c

@@ -10,6 +10,7 @@
 
 #include <common.h>
 #include <console.h>
+#include <log.h>
 #include <asm/global_data.h>
 #include <test/log.h>
 #include <test/test.h>
@@ -128,8 +129,10 @@ static int log_test_nolog_debug(struct unit_test_state *uts)
 	memset(buf, 0, BUFFSIZE);
 	console_record_reset_enable();
 	log_debug("testing %s\n", "log_debug");
+	log(LOGC_NONE, LOGL_DEBUG, "more %s\n", "log_debug");
 	gd->flags &= ~GD_FLG_RECORD;
 	ut_assertok(ut_check_console_line(uts, "testing log_debug"));
+	ut_assertok(ut_check_console_line(uts, "more log_debug"));
 	ut_assertok(ut_check_console_end(uts));
 	return 0;
 }

+ 235 - 49
test/print_ut.c

@@ -3,42 +3,52 @@
  * Copyright (c) 2012, The Chromium Authors
  */
 
-#define DEBUG
-
 #include <common.h>
 #include <command.h>
 #include <efi_api.h>
 #include <display_options.h>
 #include <log.h>
+#include <mapmem.h>
 #include <version.h>
+#include <test/suites.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+#define BUF_SIZE	0x100
 
 #define FAKE_BUILD_TAG	"jenkins-u-boot-denx_uboot_dm-master-build-aarch64" \
 			"and a lot more text to come"
 
+/* Declare a new print test */
+#define PRINT_TEST(_name, _flags)	UNIT_TEST(_name, _flags, print_test)
+
+#if CONFIG_IS_ENABLED(LIB_UUID)
 /* Test printing GUIDs */
-static void guid_ut_print(void)
+static int print_guid(struct unit_test_state *uts)
 {
-#if CONFIG_IS_ENABLED(LIB_UUID)
 	unsigned char guid[16] = {
 		1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
 	};
 	char str[40];
 
 	sprintf(str, "%pUb", guid);
-	assert(!strcmp("01020304-0506-0708-090a-0b0c0d0e0f10", str));
+	ut_assertok(strcmp("01020304-0506-0708-090a-0b0c0d0e0f10", str));
 	sprintf(str, "%pUB", guid);
-	assert(!strcmp("01020304-0506-0708-090A-0B0C0D0E0F10", str));
+	ut_assertok(strcmp("01020304-0506-0708-090A-0B0C0D0E0F10", str));
 	sprintf(str, "%pUl", guid);
-	assert(!strcmp("04030201-0605-0807-090a-0b0c0d0e0f10", str));
+	ut_assertok(strcmp("04030201-0605-0807-090a-0b0c0d0e0f10", str));
 	sprintf(str, "%pUL", guid);
-	assert(!strcmp("04030201-0605-0807-090A-0B0C0D0E0F10", str));
-#endif
+	ut_assertok(strcmp("04030201-0605-0807-090A-0B0C0D0E0F10", str));
+
+	return 0;
 }
+PRINT_TEST(print_guid, 0);
+#endif
 
+#if CONFIG_IS_ENABLED(EFI_LOADER) && !defined(API_BUILD)
 /* Test efi_loader specific printing */
-static void efi_ut_print(void)
+static int print_efi_ut(struct unit_test_state *uts)
 {
-#if CONFIG_IS_ENABLED(EFI_LOADER) && !defined(API_BUILD)
 	char str[10];
 	u8 buf[sizeof(struct efi_device_path_sd_mmc_path) +
 	       sizeof(struct efi_device_path)];
@@ -60,92 +70,268 @@ static void efi_ut_print(void)
 	dp_end->length = sizeof(struct efi_device_path);
 
 	snprintf(str, sizeof(str), "_%pD_", buf);
-	assert(!strcmp("_/SD(3)_", str));
+	ut_assertok(strcmp("_/SD(3)_", str));
 
 	/* NULL device path */
 	snprintf(str, sizeof(str), "_%pD_", NULL);
-	assert(!strcmp("_<NULL>_", str));
-#endif
+	ut_assertok(strcmp("_<NULL>_", str));
+
+	return 0;
 }
+PRINT_TEST(print_efi_ut, 0);
+#endif
 
-static int do_ut_print(struct cmd_tbl *cmdtp, int flag, int argc,
-		       char *const argv[])
+static int print_printf(struct unit_test_state *uts)
 {
 	char big_str[400];
 	int big_str_len;
 	char str[10], *s;
 	int len;
 
-	printf("%s: Testing print\n", __func__);
-
 	snprintf(str, sizeof(str), "testing");
-	assert(!strcmp("testing", str));
+	ut_assertok(strcmp("testing", str));
 
 	snprintf(str, sizeof(str), "testing but too long");
-	assert(!strcmp("testing b", str));
+	ut_assertok(strcmp("testing b", str));
 
 	snprintf(str, 1, "testing none");
-	assert(!strcmp("", str));
+	ut_assertok(strcmp("", str));
 
 	*str = 'x';
 	snprintf(str, 0, "testing none");
-	assert(*str == 'x');
+	ut_asserteq('x', *str);
 
 	sprintf(big_str, "_%ls_", L"foo");
-	assert(!strcmp("_foo_", big_str));
+	ut_assertok(strcmp("_foo_", big_str));
 
 	/* Test the banner function */
 	s = display_options_get_banner(true, str, sizeof(str));
-	assert(s == str);
-	assert(!strcmp("\n\nU-Boo\n\n", s));
+	ut_asserteq_ptr(str, s);
+	ut_assertok(strcmp("\n\nU-Boo\n\n", s));
 
 	/* Assert that we do not overwrite memory before the buffer */
 	str[0] = '`';
 	s = display_options_get_banner(true, str + 1, 1);
-	assert(s == str + 1);
-	assert(!strcmp("`", str));
+	ut_asserteq_ptr(str + 1, s);
+	ut_assertok(strcmp("`", str));
 
 	str[0] = '~';
 	s = display_options_get_banner(true, str + 1, 2);
-	assert(s == str + 1);
-	assert(!strcmp("~\n", str));
+	ut_asserteq_ptr(str + 1, s);
+	ut_assertok(strcmp("~\n", str));
 
 	/* The last two characters are set to \n\n for all buffer sizes > 2 */
 	s = display_options_get_banner(false, str, sizeof(str));
-	assert(s == str);
-	assert(!strcmp("U-Boot \n\n", s));
+	ut_asserteq_ptr(str, s);
+	ut_assertok(strcmp("U-Boot \n\n", s));
 
 	/* Give it enough space for some of the version */
 	big_str_len = strlen(version_string) - 5;
 	s = display_options_get_banner_priv(false, FAKE_BUILD_TAG, big_str,
 					    big_str_len);
-	assert(s == big_str);
-	assert(!strncmp(version_string, s, big_str_len - 3));
-	assert(!strcmp("\n\n", s + big_str_len - 3));
+	ut_asserteq_ptr(big_str, s);
+	ut_assertok(strncmp(version_string, s, big_str_len - 3));
+	ut_assertok(strcmp("\n\n", s + big_str_len - 3));
 
 	/* Give it enough space for the version and some of the build tag */
 	big_str_len = strlen(version_string) + 9 + 20;
 	s = display_options_get_banner_priv(false, FAKE_BUILD_TAG, big_str,
 					    big_str_len);
-	assert(s == big_str);
+	ut_asserteq_ptr(big_str, s);
 	len = strlen(version_string);
-	assert(!strncmp(version_string, s, len));
-	assert(!strncmp(", Build: ", s + len, 9));
-	assert(!strncmp(FAKE_BUILD_TAG, s + 9 + len, 12));
-	assert(!strcmp("\n\n", s + big_str_len - 3));
+	ut_assertok(strncmp(version_string, s, len));
+	ut_assertok(strncmp(", Build: ", s + len, 9));
+	ut_assertok(strncmp(FAKE_BUILD_TAG, s + 9 + len, 12));
+	ut_assertok(strcmp("\n\n", s + big_str_len - 3));
+
+	return 0;
+}
+PRINT_TEST(print_printf, 0);
+
+static int print_display_buffer(struct unit_test_state *uts)
+{
+	u8 *buf;
+	int i;
+
+	buf = map_sysmem(0, BUF_SIZE);
+	memset(buf, '\0', BUF_SIZE);
+	for (i = 0; i < 0x11; i++)
+		buf[i] = i * 0x11;
+
+	/* bytes */
+	console_record_reset();
+	print_buffer(0, buf, 1, 0x12, 0);
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 10 00                                            ..");
+	ut_assert_console_end();
+
+	/* line length */
+	console_record_reset();
+	print_buffer(0, buf, 1, 0x12, 8);
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77  ..\"3DUfw");
+	ut_assert_nextline("00000008: 88 99 aa bb cc dd ee ff  ........");
+	ut_assert_nextline("00000010: 10 00                    ..");
+	ut_assert_console_end();
+
+	/* long line */
+	console_record_reset();
+	buf[0x41] = 0x41;
+	print_buffer(0, buf, 1, 0x42, 0x40);
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ..\"3DUfw........................................................");
+	ut_assert_nextline("00000040: 00 41                                                                                                                                                                                            .A");
+	ut_assert_console_end();
+
+	/* address */
+	console_record_reset();
+	print_buffer(0x12345678, buf, 1, 0x12, 0);
+	ut_assert_nextline("12345678: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  ..\"3DUfw........");
+	ut_assert_nextline("12345688: 10 00                                            ..");
+	ut_assert_console_end();
+
+	/* 16-bit */
+	console_record_reset();
+	print_buffer(0, buf, 2, 9, 0);
+	ut_assert_nextline("00000000: 1100 3322 5544 7766 9988 bbaa ddcc ffee  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 0010                                     ..");
+	ut_assert_console_end();
 
-	/* Test efi_loader specific printing */
-	efi_ut_print();
+	/* 32-bit */
+	console_record_reset();
+	print_buffer(0, buf, 4, 5, 0);
+	ut_assert_nextline("00000000: 33221100 77665544 bbaa9988 ffeeddcc  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 00000010                             ....");
+	ut_assert_console_end();
 
-	/* Test printing GUIDs */
-	guid_ut_print();
+	/* 64-bit */
+	console_record_reset();
+	print_buffer(0, buf, 8, 3, 0);
+	ut_assert_nextline("00000000: 7766554433221100 ffeeddccbbaa9988  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 0000000000000010                   ........");
+	ut_assert_console_end();
+
+	/* ASCII */
+	console_record_reset();
+	buf[1] = 31;
+	buf[2] = 32;
+	buf[3] = 33;
+	for (i = 0; i < 4; i++)
+		buf[4 + i] = 126 + i;
+	buf[8] = 255;
+	print_buffer(0, buf, 1, 10, 0);
+	ut_assert_nextline("00000000: 00 1f 20 21 7e 7f 80 81 ff 99                    .. !~.....");
+	ut_assert_console_end();
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+PRINT_TEST(print_display_buffer, UT_TESTF_CONSOLE_REC);
+
+static int print_hexdump_line(struct unit_test_state *uts)
+{
+	char *linebuf;
+	u8 *buf;
+	int i;
+
+	buf = map_sysmem(0, BUF_SIZE);
+	memset(buf, '\0', BUF_SIZE);
+	for (i = 0; i < 0x11; i++)
+		buf[i] = i * 0x11;
+
+	/* Check buffer size calculations */
+	linebuf = map_sysmem(0x400, BUF_SIZE);
+	memset(linebuf, '\xff', BUF_SIZE);
+	ut_asserteq(-ENOSPC, hexdump_line(0, buf, 1, 0x10, 0, linebuf, 75));
+	ut_asserteq(-1, linebuf[0]);
+	ut_asserteq(0x10, hexdump_line(0, buf, 1, 0x10, 0, linebuf, 76));
+	ut_asserteq(0, linebuf[75]);
+	ut_asserteq(-1, linebuf[76]);
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+PRINT_TEST(print_hexdump_line, UT_TESTF_CONSOLE_REC);
+
+static int print_do_hex_dump(struct unit_test_state *uts)
+{
+	u8 *buf;
+	int i;
+
+	buf = map_sysmem(0, BUF_SIZE);
+	memset(buf, '\0', BUF_SIZE);
+	for (i = 0; i < 0x11; i++)
+		buf[i] = i * 0x11;
+
+	/* bytes */
+	console_record_reset();
+	print_hex_dump_bytes("", DUMP_PREFIX_ADDRESS, buf, 0x12);
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 10 00                                            ..");
+	ut_assert_console_end();
+
+	/* line length */
+	console_record_reset();
+	print_hex_dump("", DUMP_PREFIX_ADDRESS, 8, 1, buf, 0x12, true);
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77  ..\"3DUfw");
+	ut_assert_nextline("00000008: 88 99 aa bb cc dd ee ff  ........");
+	ut_assert_nextline("00000010: 10 00                    ..");
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	/* long line */
+	console_record_reset();
+	buf[0x41] = 0x41;
+	print_hex_dump("", DUMP_PREFIX_ADDRESS, 0x40, 1, buf, 0x42, true);
+	ut_assert_nextline("00000000: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ..\"3DUfw........................................................");
+	ut_assert_nextline("00000040: 00 41                                                                                                                                                                                            .A");
+	ut_assert_console_end();
+
+	/* 16-bit */
+	console_record_reset();
+	print_hex_dump("", DUMP_PREFIX_ADDRESS, 0, 2, buf, 0x12, true);
+	ut_assert_nextline("00000000: 1100 3322 5544 7766 9988 bbaa ddcc ffee  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 0010                                     ..");
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	/* 32-bit */
+	console_record_reset();
+	print_hex_dump("", DUMP_PREFIX_ADDRESS, 0, 4, buf, 0x14, true);
+	ut_assert_nextline("00000000: 33221100 77665544 bbaa9988 ffeeddcc  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 00000010                             ....");
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	/* 64-bit */
+	console_record_reset();
+	print_hex_dump("", DUMP_PREFIX_ADDRESS, 16, 8, buf, 0x18, true);
+	ut_assert_nextline("00000000: 7766554433221100 ffeeddccbbaa9988  ..\"3DUfw........");
+	ut_assert_nextline("00000010: 0000000000000010                   ........");
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	/* ASCII */
+	console_record_reset();
+	buf[1] = 31;
+	buf[2] = 32;
+	buf[3] = 33;
+	for (i = 0; i < 4; i++)
+		buf[4 + i] = 126 + i;
+	buf[8] = 255;
+	print_hex_dump("", DUMP_PREFIX_ADDRESS, 0, 1, buf, 10, true);
+	ut_assert_nextline("00000000: 00 1f 20 21 7e 7f 80 81 ff 99                    .. !~.....");
+	ut_assert_console_end();
+	unmap_sysmem(buf);
 
-	printf("%s: Everything went swimmingly\n", __func__);
 	return 0;
 }
+PRINT_TEST(print_do_hex_dump, UT_TESTF_CONSOLE_REC);
 
-U_BOOT_CMD(
-	ut_print,	1,	1,	do_ut_print,
-	"Very basic test of printf(), etc.",
-	""
-);
+int do_ut_print(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	struct unit_test *tests = UNIT_TEST_SUITE_START(print_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(print_test);
+
+	return cmd_ut_category("print", "print_", tests, n_ents, argc, argv);
+}

+ 47 - 8
test/ut.c

@@ -51,14 +51,37 @@ long ut_check_delta(ulong last)
 	return ut_check_free() - last;
 }
 
+static int readline_check(struct unit_test_state *uts)
+{
+	int ret;
+
+	ret = console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+	if (ret == -ENOSPC) {
+		ut_fail(uts, __FILE__, __LINE__, __func__,
+			"Console record buffer too small - increase CONFIG_CONSOLE_RECORD_OUT_SIZE");
+		return ret;
+	}
+
+	return 0;
+}
+
 int ut_check_console_line(struct unit_test_state *uts, const char *fmt, ...)
 {
 	va_list args;
+	int len;
+	int ret;
 
 	va_start(args, fmt);
-	vsnprintf(uts->expect_str, sizeof(uts->expect_str), fmt, args);
+	len = vsnprintf(uts->expect_str, sizeof(uts->expect_str), fmt, args);
 	va_end(args);
-	console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+	if (len >= sizeof(uts->expect_str)) {
+		ut_fail(uts, __FILE__, __LINE__, __func__,
+			"unit_test_state->expect_str too small");
+		return -EOVERFLOW;
+	}
+	ret = readline_check(uts);
+	if (ret < 0)
+		return ret;
 
 	return strcmp(uts->expect_str, uts->actual_str);
 }
@@ -66,11 +89,20 @@ int ut_check_console_line(struct unit_test_state *uts, const char *fmt, ...)
 int ut_check_console_linen(struct unit_test_state *uts, const char *fmt, ...)
 {
 	va_list args;
+	int len;
+	int ret;
 
 	va_start(args, fmt);
-	vsnprintf(uts->expect_str, sizeof(uts->expect_str), fmt, args);
+	len = vsnprintf(uts->expect_str, sizeof(uts->expect_str), fmt, args);
 	va_end(args);
-	console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+	if (len >= sizeof(uts->expect_str)) {
+		ut_fail(uts, __FILE__, __LINE__, __func__,
+			"unit_test_state->expect_str too small");
+		return -EOVERFLOW;
+	}
+	ret = readline_check(uts);
+	if (ret < 0)
+		return ret;
 
 	return strncmp(uts->expect_str, uts->actual_str,
 		       strlen(uts->expect_str));
@@ -78,19 +110,26 @@ int ut_check_console_linen(struct unit_test_state *uts, const char *fmt, ...)
 
 int ut_check_skipline(struct unit_test_state *uts)
 {
+	int ret;
+
 	if (!console_record_avail())
 		return -ENFILE;
-	console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+	ret = readline_check(uts);
+	if (ret < 0)
+		return ret;
 
 	return 0;
 }
 
 int ut_check_console_end(struct unit_test_state *uts)
 {
+	int ret;
+
 	if (!console_record_avail())
 		return 0;
-
-	console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+	ret = readline_check(uts);
+	if (ret < 0)
+		return ret;
 
 	return 1;
 }
@@ -112,7 +151,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes)
 		if (str[8] != ':' || str[9] != ' ')
 			return 1;
 
-		bytes = len - 8 - 2 - 3 * 16 - 4;
+		bytes = len - 8 - 2 - 3 * 16 - 2;
 		upto += bytes;
 	}