[Concept,09/13] console: Add the putsn() API for length-based string output

Message ID 20260204001002.2638622-10-sjg@u-boot.org
State New
Headers
Series Add putsn() for length-based console output |

Commit Message

Simon Glass Feb. 4, 2026, 12:09 a.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add putsn() function that outputs exactly a specified number of
characters, regardless of any nul characters that may be present in the
data. This is useful for printing substrings or binary data without
allocating temporary buffers.

The function follows the same routing logic as puts(), supporting all
console modes (sandbox, debug UART, silent console, etc.). The
underlying infrastructure already supports length-based operations, so
this mainly adds the public API wrapper.

The feature is controlled by CONFIG_CONSOLE_PUTSN.

Example use cases:
- Print a substring: putsn(str + offset, count)
- Print binary data with embedded nuls: putsn(data, size)
- Print fixed-width fields: putsn(field, width)

Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 common/console.c      | 97 +++++++++++++++++++++++++++++++++++++------
 include/stdio.h       | 12 ++++++
 test/common/console.c | 48 +++++++++++++++++++++
 3 files changed, 144 insertions(+), 13 deletions(-)
  

Patch

diff --git a/common/console.c b/common/console.c
index 67d87c6edfa..fbf2c875428 100644
--- a/common/console.c
+++ b/common/console.c
@@ -88,11 +88,11 @@  static void console_record_putc(char c)
 {
 }
 
-static void __maybe_unused console_record_putsn(const char *s, int len)
+static void console_record_puts(const char *s)
 {
 }
 
-static void console_record_puts(const char *s)
+static void console_record_putsn(const char *s, int len)
 {
 }
 
@@ -358,17 +358,26 @@  static void console_puts(int file, bool use_pager, const char *s)
 
 static void console_putsn_pager(int file, const char *s, int len)
 {
-	struct stdio_dev *dev;
-	int i;
+	if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) {
+		/*
+		 * Pager only works with nul-terminated strings, so output
+		 * character by character for length-based strings
+		 */
+		while (len--)
+			console_putc_pager(file, *s++);
+	} else {
+		struct stdio_dev *dev;
+		int i;
 
-	for_each_console_dev(i, file, dev) {
-		if (dev->putsn)
-			dev->putsn(dev, s, len);
-		else if (dev->puts)
-			dev->puts(dev, s);
-		else
-			while (len--)
-				dev->putc(dev, *s++);
+		for_each_console_dev(i, file, dev) {
+			if (CONFIG_IS_ENABLED(CONSOLE_PUTSN) && dev->putsn)
+				dev->putsn(dev, s, len);
+			else if (dev->puts)
+				dev->puts(dev, s);
+			else
+				while (len--)
+					dev->putc(dev, *s++);
+		}
 	}
 }
 
@@ -475,6 +484,17 @@  static inline void console_puts_pager(int file, const char *s)
 	stdio_devices[file]->puts(stdio_devices[file], s);
 }
 
+static inline void console_putsn_pager(int file, const char *s, int len)
+{
+	if (CONFIG_IS_ENABLED(CONSOLE_PUTSN) && stdio_devices[file]->putsn)
+		stdio_devices[file]->putsn(stdio_devices[file], s, len);
+	else if (stdio_devices[file]->puts)
+		stdio_devices[file]->puts(stdio_devices[file], s);
+	else
+		while (len--)
+			stdio_devices[file]->putc(stdio_devices[file], *s++);
+}
+
 #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT
 static inline void console_flush(int file)
 {
@@ -642,6 +662,9 @@  void fputs(int file, const char *s)
 
 void fputsn(int file, const char *s, int len)
 {
+	if (!CONFIG_IS_ENABLED(CONSOLE_PUTSN))
+		return;
+
 	if ((unsigned int)file < MAX_FILES)
 		console_putsn_pager(file, s, len);
 }
@@ -784,8 +807,8 @@  static void print_pre_console_buffer(int flushpoint)
 }
 #else
 static inline void pre_console_putc(const char c) {}
-static inline void pre_console_putsn(const char *s, int len) {}
 static inline void pre_console_puts(const char *s) {}
+static inline void pre_console_putsn(const char *s, int len) {}
 static inline void print_pre_console_buffer(int flushpoint) {}
 #endif
 
@@ -830,8 +853,56 @@  void putc(const char c)
 	}
 }
 
+void putsn(const char *s, int len)
+{
+	if (!CONFIG_IS_ENABLED(CONSOLE_PUTSN))
+		return;
+
+	if (!gd)
+		return;
+
+	console_record_putsn(s, len);
+
+	/* sandbox can send characters to stdout before it has a console */
+	if (IS_ENABLED(CONFIG_SANDBOX) && !(gd->flags & GD_FLG_SERIAL_READY)) {
+		os_putsn(s, len);
+		return;
+	}
+
+	if (IS_ENABLED(CONFIG_DEBUG_UART) && !(gd->flags & GD_FLG_SERIAL_READY)) {
+		printasciin(s, len);
+		return;
+	}
+
+	if (IS_ENABLED(CONFIG_SILENT_CONSOLE) && (gd->flags & GD_FLG_SILENT)) {
+		if (!(gd->flags & GD_FLG_DEVINIT))
+			pre_console_putsn(s, len);
+		return;
+	}
+
+	if (IS_ENABLED(CONFIG_DISABLE_CONSOLE) && (gd->flags & GD_FLG_DISABLE_CONSOLE))
+		return;
+
+	if (!(gd->flags & GD_FLG_HAVE_CONSOLE))
+		return pre_console_putsn(s, len);
+
+	if (gd->flags & GD_FLG_DEVINIT) {
+		/* Send to the standard output */
+		fputsn(stdout, s, len);
+	} else {
+		/* Send directly to the handler */
+		pre_console_putsn(s, len);
+		serial_putsn(s, len);
+	}
+}
+
 void puts(const char *s)
 {
+	if (CONFIG_IS_ENABLED(CONSOLE_PUTSN)) {
+		putsn(s, strlen(s));
+		return;
+	}
+
 	if (!gd)
 		return;
 
diff --git a/include/stdio.h b/include/stdio.h
index d42fdd2728c..802cedb8f24 100644
--- a/include/stdio.h
+++ b/include/stdio.h
@@ -12,6 +12,18 @@  int tstc(void);
 #if !defined(CONFIG_XPL_BUILD) || CONFIG_IS_ENABLED(SERIAL)
 void putc(const char c);
 void puts(const char *s);
+
+/**
+ * putsn() - Output a string with specified length
+ *
+ * This outputs exactly @len characters from @s, regardless of any nul
+ * characters that may be present. This is useful for printing substrings
+ * or binary data with embedded nuls without allocating temporary buffers.
+ *
+ * @s: String to output (need not be nul-terminated)
+ * @len: Number of characters to output
+ */
+void putsn(const char *s, int len);
 #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT
 void flush(void);
 #else
diff --git a/test/common/console.c b/test/common/console.c
index 5558343d45b..c2ff8210a43 100644
--- a/test/common/console.c
+++ b/test/common/console.c
@@ -206,3 +206,51 @@  static int console_test_calc_lines_serial_tty(struct unit_test_state *uts)
 	return 0;
 }
 COMMON_TEST(console_test_calc_lines_serial_tty, 0);
+
+/* Test putsn() with basic string */
+static int console_test_putsn_basic(struct unit_test_state *uts)
+{
+	putsn("hello", 5);
+	ut_assertok(ut_check_console_line(uts, "hello"));
+	ut_assert_console_end();
+
+	return 0;
+}
+COMMON_TEST(console_test_putsn_basic, UTF_CONSOLE);
+
+/* Test putsn() with substring */
+static int console_test_putsn_substring(struct unit_test_state *uts)
+{
+	const char *str = "hello world";
+
+	putsn(str, 5);
+	ut_assertok(ut_check_console_line(uts, "hello"));
+	ut_assert_console_end();
+
+	return 0;
+}
+COMMON_TEST(console_test_putsn_substring, UTF_CONSOLE);
+
+/* Test putsn() with zero length */
+static int console_test_putsn_zero(struct unit_test_state *uts)
+{
+	putsn("test", 0);
+	ut_assert_console_end();
+
+	return 0;
+}
+COMMON_TEST(console_test_putsn_zero, UTF_CONSOLE);
+
+/* Test putsn() outputs exactly len characters */
+static int console_test_putsn_exact_len(struct unit_test_state *uts)
+{
+	const char *str = "hello world";
+
+	/* Output exactly 5 characters from a longer string */
+	putsn(str, 5);
+	ut_assertok(ut_check_console_line(uts, "hello"));
+	ut_assert_console_end();
+
+	return 0;
+}
+COMMON_TEST(console_test_putsn_exact_len, UTF_CONSOLE);