From patchwork Wed Feb 4 00:09:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1803 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770163859; bh=LHxasfJxVobFul6aEI5tLMdqvbbojYjPmf8Rv7WD7Rw=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=lKyfOJnanfnWJux3opBHjQOokAbujsBDeuQNbyn1Mx+ilQ2DkAKf0FiAktMHlae4j qm+djoZqgHJ9kRkeiWB4AEqwCdURG3zjPPzCLIYCYqFh6IZWNR830vsgUROmwcWw3F CXf5G9YmW3kXEMi5hJFh0HWu1XzDaahRCUQ6K2kC8OpPBIU32ocMHc8QUmeFSnVJJR NkPZEWpAnC4lp0JGy0bNd8foWDAxWN/vp5O1KO4Cqa/ymt+KszLhgrr1zseeIjCw+K tq/l4Tt47Vmij3xiXEbx2wnHrXL/PKASAaG/ooI94jcSwvjD0ew2R/HSplkp6MRI+Z BB9LpkHcWzCrg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2985F69917 for ; Tue, 3 Feb 2026 17:10:59 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Q-w-B9B5DnL5 for ; Tue, 3 Feb 2026 17:10:59 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770163859; bh=LHxasfJxVobFul6aEI5tLMdqvbbojYjPmf8Rv7WD7Rw=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=lKyfOJnanfnWJux3opBHjQOokAbujsBDeuQNbyn1Mx+ilQ2DkAKf0FiAktMHlae4j qm+djoZqgHJ9kRkeiWB4AEqwCdURG3zjPPzCLIYCYqFh6IZWNR830vsgUROmwcWw3F CXf5G9YmW3kXEMi5hJFh0HWu1XzDaahRCUQ6K2kC8OpPBIU32ocMHc8QUmeFSnVJJR NkPZEWpAnC4lp0JGy0bNd8foWDAxWN/vp5O1KO4Cqa/ymt+KszLhgrr1zseeIjCw+K tq/l4Tt47Vmij3xiXEbx2wnHrXL/PKASAaG/ooI94jcSwvjD0ew2R/HSplkp6MRI+Z BB9LpkHcWzCrg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 18C6469911 for ; Tue, 3 Feb 2026 17:10:59 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770163857; bh=VZ32tMvsgfPD1lnkwIQX0D3zBHcePxKM2f5h32fu+CU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tVlM9et3nAx1BLNj5wPgygftd35P6qeMjmTQKMehlpBmjtEzeNNOcAvDbn9q3AFgw a3TsmL63S+CVCmBJFY85jql1a2JRDaHbB24Td7RuvfjES8O95WgwsED/ysVLDH1Qad n0CY4637WbuyzNR5AyL8X5mmjUwdMtDLZWyT0fBIChw8sivIwXbIby1o2wq7y2XdFv 1+N3+ygQE8X841XGRR4vD8d9eop8qylVLpM7kpvfnVLGX9UPFoaXDjoEOxwQRHrXeW Bj+cg7ogss34nuydQtzSkBq32/5aFLZ/ereENCLWx65bOGfvs0eCk1EZNEXgQsO/3n OONElJsvu6oDg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2AA0D69868; Tue, 3 Feb 2026 17:10:57 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id wjILhpyHNRZ2; Tue, 3 Feb 2026 17:10:57 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770163853; bh=zLa8jJimBKgmn6FE0OCvOuwZPNGimwEmAKXe2XooFOk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qWUtMoR2pql+H+2dMJ6ktwZF4/BLLwFkgzc7y+uSU63cgycN0PI5qOUWEJP4qbhYd nxu2m94rjb2hAGJvnXzFu72+8HYzod40AhdBpdSEv38wgDDTYGhNcMFzE7kR2K8Crj xSHbcr06h+NZ2sTVRO8zrXM9uhGSM27okuxGkXjJ1G7Xf+mD++HsQI+lo6tuq4zrUq GBvUQzBCDPsKZu29pNhJ3wjvHrgTLNGObSFZRY/O+YkbiGFaboeRiCHSng1cKz1rOn Fbi+RMsRFRuJ5YqRKt7ikDQSMgXS9/ZYm6bJNv8oeFnWzrkIaToDe0f1WxuSGftUAa DhBV/mmRdPnTg== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id CFC0269909; Tue, 3 Feb 2026 17:10:52 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Tue, 3 Feb 2026 17:09:53 -0700 Message-ID: <20260204001002.2638622-10-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260204001002.2638622-1-sjg@u-boot.org> References: <20260204001002.2638622-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6L6WW2SV4FPXDLV2LQUXFRDZVFUHQBSO X-Message-ID-Hash: 6L6WW2SV4FPXDLV2LQUXFRDZVFUHQBSO X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Sonnet 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 09/13] console: Add the putsn() API for length-based string output List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass 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 Signed-off-by: Simon Glass --- common/console.c | 97 +++++++++++++++++++++++++++++++++++++------ include/stdio.h | 12 ++++++ test/common/console.c | 48 +++++++++++++++++++++ 3 files changed, 144 insertions(+), 13 deletions(-) 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);