[PATCH 00/13] Add putsn() for length-based console output
From: Simon Glass <simon.glass@canonical.com> This series adds putsn() which outputs exactly a specified number of characters from a buffer, regardless of any nul characters that may be present. This is useful for printing substrings or binary data without allocating temporary buffers. The infrastructure for length-based output already exists at the driver level (membuf_put(), __serial_puts()), so this series mainly adds the public API and necessary wrapper functions at the console layer. The feature is controlled by CONFIG_CONSOLE_PUTSN and is enabled by default for sandbox. Simon Glass (13): console: Add a Kconfig option for putsn() support serial: Support length-based serial output console: Add length-based console-recording support console: Add length-based pre-console support console: Add length-based debug UART support sandbox: Add support for length-based console output console: Add putsn() support to struct stdio_dev console: Add vidconsole support for length-based output console: Add the putsn() API for length-based string output console: Shorten the console-error-output function names console: Add err_putsn() for length-based error output console: Add putsn() to ulib CI: Add artifacts for the 'make check' CI jobs .gitlab-ci.yml | 9 ++ arch/sandbox/cpu/os.c | 8 +- common/Kconfig | 9 ++ common/console.c | 177 +++++++++++++++++++++++++++--- drivers/serial/serial-uclass.c | 34 ++++++ drivers/video/console_truetype.c | 6 +- drivers/video/vidconsole-uclass.c | 33 +++--- include/console.h | 23 +++- include/debug_uart.h | 17 +++ include/os.h | 13 +++ include/serial.h | 12 ++ include/stdio.h | 12 ++ include/stdio_dev.h | 12 ++ include/u-boot-lib.h | 15 +++ lib/ulib/ulib.c | 10 ++ test/common/console.c | 48 ++++++++ test/dm/serial.c | 20 ++++ 17 files changed, 416 insertions(+), 42 deletions(-) -- 2.43.0 base-commit: 8559ca571a16dca20274e130562a116a63846de5 branch: puts
From: Simon Glass <simon.glass@canonical.com> Add CONFIG_CONSOLE_PUTSN option to enable the putsn() function for length-based string output. This allows printing strings without requiring nul-termination. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/Kconfig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/Kconfig b/common/Kconfig index c709df753bf..fd1f9cc5bcb 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -54,6 +54,15 @@ config CONSOLE_RECORD_IN_SIZE The buffer is allocated immediately after the malloc() region is ready. +config CONSOLE_PUTSN + bool "Enable putsn() function for length-based output" + default y if SANDBOX + help + Enable the putsn() function which outputs a string with a + specified length without requiring nul-termination. This is + useful for printing substrings or binary data without + allocating temporary buffers. + config SYS_CBSIZE int "Console input buffer size" default 2048 if ARCH_TEGRA || ARCH_VERSAL || ARCH_ZYNQ || ARCH_ZYNQMP || \ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add serial_putsn() to output a string with a specified length through the serial port. The serial uclass already supports length-based writes via __serial_puts(), so this just provides a public wrapper function. Add a test to check it. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/serial/serial-uclass.c | 34 ++++++++++++++++++++++++++++++++++ include/serial.h | 12 ++++++++++++ test/dm/serial.c | 20 ++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/drivers/serial/serial-uclass.c b/drivers/serial/serial-uclass.c index 8d330d687a3..93fb4a0c2e3 100644 --- a/drivers/serial/serial-uclass.c +++ b/drivers/serial/serial-uclass.c @@ -309,6 +309,34 @@ static void _serial_puts(struct udevice *dev, const char *str) } while (*str); } +static void _serial_putsn(struct udevice *dev, const char *str, size_t len) +{ + struct dm_serial_ops *ops = serial_get_ops(dev); + + if (!CONFIG_IS_ENABLED(SERIAL_PUTS) || !ops->puts) { + while (len--) + _serial_putc(dev, *str++); + return; + } + + while (len) { + const char *newline = memchr(str, '\n', len); + size_t seg_len = newline ? newline - str : len; + + if (__serial_puts(dev, str, seg_len)) + return; + + if (newline && __serial_puts(dev, "\r\n", 2)) + return; + + if (IS_ENABLED(CONFIG_CONSOLE_FLUSH_ON_NEWLINE) && newline) + _serial_flush(dev); + + str += seg_len + !!newline; + len -= seg_len + !!newline; + } +} + static int __serial_getc(struct udevice *dev) { struct dm_serial_ops *ops = serial_get_ops(dev); @@ -391,6 +419,12 @@ void serial_puts(const char *str) _serial_puts(gd->cur_serial_dev, str); } +void serial_putsn(const char *str, int len) +{ + if (gd->cur_serial_dev) + _serial_putsn(gd->cur_serial_dev, str, len); +} + #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT void serial_flush(void) { diff --git a/include/serial.h b/include/serial.h index 51977d8d7b7..66bf17a7cc0 100644 --- a/include/serial.h +++ b/include/serial.h @@ -392,6 +392,18 @@ void serial_setbrg(void); void serial_putc(const char ch); void serial_putc_raw(const char ch); void serial_puts(const char *str); + +/** + * serial_putsn() - Write a string with specified length to the serial console + * + * This outputs exactly @len characters from @str, regardless of any nul + * characters that may be present. This is useful for printing substrings or + * binary data that may contain embedded nuls. + * + * @str: String to output (need not be nul-terminated) + * @len: Number of characters to output + */ +void serial_putsn(const char *str, int len); #if defined(CONFIG_CONSOLE_FLUSH_SUPPORT) && CONFIG_IS_ENABLED(DM_SERIAL) void serial_flush(void); #else diff --git a/test/dm/serial.c b/test/dm/serial.c index 4acb14f41bc..8ed18726dd7 100644 --- a/test/dm/serial.c +++ b/test/dm/serial.c @@ -88,3 +88,23 @@ static int dm_test_serial(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_serial, UTF_SCAN_FDT); + +/* Test serial_putsn() function */ +static int dm_test_serial_putsn(struct unit_test_state *uts) +{ + const char *test_str = "testing string"; + size_t test_len = 4; + size_t start, written; + + /* Test that serial_putsn() writes the correct number of characters */ + sandbox_serial_endisable(false); + start = sandbox_serial_written(); + serial_putsn(test_str, test_len); + sandbox_serial_endisable(true); + + written = sandbox_serial_written() - start; + ut_asserteq(test_len, written); + + return 0; +} +DM_TEST(dm_test_serial_putsn, UTF_SCAN_FDT); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Console recording uses membuf_put() which already supports length-based writes. Add console_record_putsn() to accept a length parameter directly instead of calling strlen() internally. Refactor console_record_puts() to use console_record_putsn() to reduce code duplication. Since console recording is a size-increasing feature for testing, this refactoring is appropriate. This provides the foundation for a public putsn() API that can output strings without requiring nul-termination. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/console.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/common/console.c b/common/console.c index 6ce2c4d361d..581c383ce5e 100644 --- a/common/console.c +++ b/common/console.c @@ -43,12 +43,11 @@ static void console_record_putc(const char c) } } -static void console_record_puts(const char *s) +static void console_record_putsn(const char *s, int len) { if (!(gd->flags & GD_FLG_RECORD)) return; if (gd->console_out.start) { - int len = strlen(s); int written; written = membuf_put((struct membuf *)&gd->console_out, s, len); @@ -59,6 +58,11 @@ static void console_record_puts(const char *s) } } +static void console_record_puts(const char *s) +{ + console_record_putsn(s, strlen(s)); +} + static int console_record_getc(void) { if (!(gd->flags & GD_FLG_RECORD)) @@ -84,6 +88,10 @@ 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) { } -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add pre_console_putsn() for length-based output to the pre-console buffer. This allows putsn() to work correctly before the console is fully initialised. Refactor pre_console_puts() to use pre_console_putsn() to reduce code duplication. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/console.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/console.c b/common/console.c index 581c383ce5e..c058d3d30a2 100644 --- a/common/console.c +++ b/common/console.c @@ -721,15 +721,20 @@ static void pre_console_putc(const char c) unmap_sysmem(buffer); } -static void pre_console_puts(const char *s) +static void pre_console_putsn(const char *s, int len) { if (gd->precon_buf_idx < 0) return; - while (*s) + while (len--) pre_console_putc(*s++); } +static void pre_console_puts(const char *s) +{ + pre_console_putsn(s, strlen(s)); +} + static void print_pre_console_buffer(int flushpoint) { long in = 0, out = 0; @@ -762,6 +767,7 @@ 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 print_pre_console_buffer(int flushpoint) {} #endif -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add printasciin() to support length-based output through the debug UART. This allows putsn() to work correctly with the early debug console. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/debug_uart.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/debug_uart.h b/include/debug_uart.h index 714b369e6fe..f5664271f1c 100644 --- a/include/debug_uart.h +++ b/include/debug_uart.h @@ -83,6 +83,17 @@ void printch(int ch); */ void printascii(const char *str); +/** + * printasciin() - Output a string with specified length to the debug UART + * + * This outputs exactly @len characters from @str, regardless of any nul + * characters that may be present. + * + * @str: String to output (need not be nul-terminated) + * @len: Number of characters to output + */ +void printasciin(const char *str, int len); + /** * printhex2() - Output a 2-digit hex value * @@ -150,6 +161,12 @@ void printdec(unsigned int value); while (*str) \ _printch(*str++); \ } \ +\ + void printasciin(const char *str, int len) \ + { \ + while (len--) \ + _printch(*str++); \ + } \ \ static inline void printhex1(unsigned int digit) \ { \ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add os_putsn() to support length-based output in sandbox environments. This allows putsn() to work correctly before the serial console is initialised. The implementation uses os_write() directly for efficiency, and os_puts() is refactored to use os_putsn() to reduce code duplication. Note this changes sandbox to write all characters in one call. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/sandbox/cpu/os.c | 8 ++++++-- include/os.h | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c index 5278ce55766..142b685e031 100644 --- a/arch/sandbox/cpu/os.c +++ b/arch/sandbox/cpu/os.c @@ -827,10 +827,14 @@ void os_putc(int ch) os_write(1, &ch, 1); } +void os_putsn(const char *str, int len) +{ + os_write(1, str, len); +} + void os_puts(const char *str) { - while (*str) - os_putc(*str++); + os_putsn(str, strlen(str)); } void os_flush(void) diff --git a/include/os.h b/include/os.h index 3ea88230af3..bc556f2195f 100644 --- a/include/os.h +++ b/include/os.h @@ -385,6 +385,19 @@ void os_putc(int ch); */ void os_puts(const char *str); +/** + * os_putsn() - write a string with length to controlling OS terminal + * + * This bypasses the U-Boot console support and writes directly to the OS + * stdout file descriptor. + * + * Outputs exactly @len characters from @str, regardless of any nul characters. + * + * @str: String to write (need not be nul-terminated) + * @len: Number of characters to write + */ +void os_putsn(const char *str, int len); + /** * os_flush() - flush controlling OS terminal * -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add an optional putsn() method to struct stdio_dev to allow devices to provide optimised length-based output. The field is added unconditionally but only used when CONFIG_CONSOLE_PUTSN is enabled. Add console_putsn_pager() and fputsn() to route length-based output through console devices. If a device provides a putsn method, it is used; otherwise, the output falls back to calling putc in a loop. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/console.c | 31 ++++++++++++++++++++++++------- include/stdio_dev.h | 12 ++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/common/console.c b/common/console.c index c058d3d30a2..67d87c6edfa 100644 --- a/common/console.c +++ b/common/console.c @@ -271,6 +271,7 @@ static int console_tstc(int file) } static void console_puts_pager(int file, const char *s); +static void console_putsn_pager(int file, const char *s, int len); static void console_putc_pager(int file, const char c) { @@ -355,18 +356,28 @@ 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; + + 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++); + } +} + static void console_puts_pager(int file, const char *s) { if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) { console_puts(file, true, s); } else { - struct stdio_dev *dev; - int i; - - for_each_console_dev(i, file, dev) { - if (dev->puts != NULL) - dev->puts(dev, s); - } + console_putsn_pager(file, s, strlen(s)); } } @@ -629,6 +640,12 @@ void fputs(int file, const char *s) console_puts_pager(file, s); } +void fputsn(int file, const char *s, int len) +{ + if ((unsigned int)file < MAX_FILES) + console_putsn_pager(file, s, len); +} + #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT void fflush(int file) { diff --git a/include/stdio_dev.h b/include/stdio_dev.h index 4e3c4708f80..2b2334c9918 100644 --- a/include/stdio_dev.h +++ b/include/stdio_dev.h @@ -38,6 +38,18 @@ struct stdio_dev { void (*putc)(struct stdio_dev *dev, const char c); /* To put a string (accelerator) */ void (*puts)(struct stdio_dev *dev, 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 an optional accelerator - if + * NULL, the console will fall back to calling putc() in a loop. + * + * @dev: Device to output to + * @s: String to output (need not be nul-terminated) + * @len: Number of characters to output + */ + void (*putsn)(struct stdio_dev *dev, const char *s, int len); #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT /* To flush output queue */ void (*flush)(struct stdio_dev *dev); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add putsn() support to the video console driver to enable efficient rendering of length-specified strings. The vidconsole infrastructure already supports length-based operations through vidconsole_put_stringn(), so this just adds the stdio_dev wrapper. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/video/vidconsole-uclass.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index d13b4eac272..3d440e8e672 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -589,7 +589,7 @@ static void vidconsole_putc(struct stdio_dev *sdev, const char ch) } } -static void vidconsole_puts(struct stdio_dev *sdev, const char *s) +static void vidconsole_putsn(struct stdio_dev *sdev, const char *s, int len) { struct udevice *dev = sdev->priv; struct vidconsole_priv *priv = dev_get_uclass_priv(dev); @@ -597,12 +597,12 @@ static void vidconsole_puts(struct stdio_dev *sdev, const char *s) if (priv->quiet) return; - ret = vidconsole_put_string(dev, NULL, s); + ret = vidconsole_put_stringn(dev, NULL, s, len); if (ret) { #ifdef DEBUG char str[30]; - snprintf(str, sizeof(str), "[vc err: puts %d]", ret); + snprintf(str, sizeof(str), "[vc err: putsn %d]", ret); console_puts_select_stderr(true, str); #endif } @@ -614,6 +614,11 @@ static void vidconsole_puts(struct stdio_dev *sdev, const char *s) } } +static void vidconsole_puts(struct stdio_dev *sdev, const char *s) +{ + vidconsole_putsn(sdev, s, strlen(s)); +} + void vidconsole_list_fonts(struct udevice *dev) { struct vidfont_info info; @@ -950,6 +955,7 @@ static int vidconsole_post_probe(struct udevice *dev) sdev->flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_DM; sdev->putc = vidconsole_putc; sdev->puts = vidconsole_puts; + sdev->putsn = vidconsole_putsn; sdev->priv = dev; return stdio_register(sdev); -- 2.43.0
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(-) 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); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The current names are quite verbose. Rename console_puts_select_stderr() to err_puts() and console_printf_select_stderr() to err_printf() Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/console.c | 6 +++--- drivers/video/console_truetype.c | 6 ++---- drivers/video/vidconsole-uclass.c | 21 ++++++++++----------- include/console.h | 8 ++++---- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/common/console.c b/common/console.c index fbf2c875428..f9669a0e028 100644 --- a/common/console.c +++ b/common/console.c @@ -311,13 +311,13 @@ static void console_puts_select(int file, bool serial_only, const char *s) } } -void console_puts_select_stderr(bool serial_only, const char *s) +void err_puts(bool serial_only, const char *s) { if (gd->flags & GD_FLG_DEVINIT) console_puts_select(stderr, serial_only, s); } -int console_printf_select_stderr(bool serial_only, const char *fmt, ...) +int err_printf(bool serial_only, const char *fmt, ...) { char buf[CONFIG_SYS_PBSIZE]; va_list args; @@ -330,7 +330,7 @@ int console_printf_select_stderr(bool serial_only, const char *fmt, ...) */ ret = vscnprintf(buf, sizeof(buf), fmt, args); va_end(args); - console_puts_select_stderr(serial_only, buf); + err_puts(serial_only, buf); return ret; } diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 25df22e31b0..913ed4208e4 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -441,14 +441,12 @@ static int console_truetype_putc_xy(struct udevice *dev, void *vctx, uint x, last_cp = pos->cp; kern = stbtt_GetCodepointKernAdvance(font, last_cp, cp); if (_DEBUG) { - console_printf_select_stderr(true, "kern %c (%02x)", - last_cp, last_cp); + err_printf(true, "kern %c (%02x)", last_cp, last_cp); } xpos += met->scale * kern; } if (_DEBUG) { - console_printf_select_stderr(true, " %c (%02x)\n", - cp >= ' ' ? cp : ' ', cp); + err_printf(true, " %c (%02x)\n", cp >= ' ' ? cp : ' ', cp); } /* diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index 3d440e8e672..fc2b443d0bd 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -122,7 +122,7 @@ static void vidconsole_newline(struct udevice *dev, struct vidconsole_ctx *ctx) ret = video_sync(dev->parent, false); if (ret) { #ifdef DEBUG - console_puts_select_stderr(true, "[vc err: video_sync]"); + err_puts(true, "[vc err: video_sync]"); #endif } } @@ -333,7 +333,7 @@ static void vidconsole_escape_char(struct udevice *dev, ret = video_sync(dev->parent, false); if (ret) { #ifdef DEBUG - console_puts_select_stderr(true, "[vc err: video_sync]"); + err_puts(true, "[vc err: video_sync]"); #endif } ctx->ycur = 0; @@ -460,11 +460,10 @@ static int vidconsole_output_glyph(struct udevice *dev, { int ret; - if (_DEBUG) { - console_printf_select_stderr(true, - "glyph last_ch '%c': ch '%c' (%02x): ", - ctx->last_ch, ch >= ' ' ? ch : ' ', ch); - } + if (_DEBUG) + err_printf(true, "glyph last_ch '%c': ch '%c' (%02x): ", + ctx->last_ch, ch >= ' ' ? ch : ' ', ch); + /* * Failure of this function normally indicates an unsupported * colour depth. Check this and return an error to help with @@ -578,13 +577,13 @@ static void vidconsole_putc(struct stdio_dev *sdev, const char ch) ret = vidconsole_put_char(dev, NULL, ch); if (ret) { #ifdef DEBUG - console_puts_select_stderr(true, "[vc err: putc]"); + err_puts(true, "[vc err: putc]"); #endif } ret = video_sync(dev->parent, false); if (ret) { #ifdef DEBUG - console_puts_select_stderr(true, "[vc err: video_sync]"); + err_puts(true, "[vc err: video_sync]"); #endif } } @@ -603,13 +602,13 @@ static void vidconsole_putsn(struct stdio_dev *sdev, const char *s, int len) char str[30]; snprintf(str, sizeof(str), "[vc err: putsn %d]", ret); - console_puts_select_stderr(true, str); + err_puts(true, str); #endif } ret = video_sync(dev->parent, false); if (ret) { #ifdef DEBUG - console_puts_select_stderr(true, "[vc err: video_sync]"); + err_puts(true, "[vc err: video_sync]"); #endif } } diff --git a/include/console.h b/include/console.h index 209b67f3535..83b8d4f642c 100644 --- a/include/console.h +++ b/include/console.h @@ -159,7 +159,7 @@ static inline bool console_record_isempty(void) int console_announce_r(void); /** - * console_puts_select_stderr() - Output a string to selected console devices + * err_puts() - Output a string to selected console devices * * This writes to stderr only. It is useful for outputting errors * @@ -167,10 +167,10 @@ int console_announce_r(void); * else * @s: String to output */ -void console_puts_select_stderr(bool serial_only, const char *s); +void err_puts(bool serial_only, const char *s); /** - * console_printf_select_stderr() - Output a formatted string to selected devs + * err_printf() - Output a formatted string to selected devs * * This writes to stderr only. It is useful for outputting errors. Note that it * uses its own buffer, separate from the print buffer, to allow printing @@ -181,7 +181,7 @@ void console_puts_select_stderr(bool serial_only, const char *s); * @fmt: Printf format string, followed by format arguments * Return: number of characters written */ -int console_printf_select_stderr(bool serial_only, const char *fmt, ...) +int err_printf(bool serial_only, const char *fmt, ...) __attribute__ ((format (__printf__, 2, 3))); /** -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add err_putsn() to output errors with a specified length, which is more efficient when the length is already known (e.g., from snprintf()). Create console_putsn_select() which uses device putsn() methods when CONFIG_CONSOLE_PUTSN is enabled, falling back to puts() otherwise. Refactor err_puts() to call err_putsn() and update err_printf() to use the length returned by vscnprintf(). Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- common/console.c | 59 +++++++++++++++++++++++++++++++++++++++-------- include/console.h | 15 ++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/common/console.c b/common/console.c index f9669a0e028..96e8977ce06 100644 --- a/common/console.c +++ b/common/console.c @@ -291,30 +291,56 @@ static void console_putc_pager(int file, const char c) } /** - * console_puts_select() - Output a string to all console devices + * console_putsn_select() - Output a string with length to console devices * * @file: File number to output to (e,g, stdout, see stdio.h) * @serial_only: true to output only to serial, false to output to everything * else * @s: String to output + * @len: Length of string to output */ -static void console_puts_select(int file, bool serial_only, const char *s) +static void console_putsn_select(int file, bool serial_only, const char *s, + int len) { + struct stdio_dev *sdev; int i; struct stdio_dev *dev; for_each_console_dev(i, file, dev) { bool is_serial = console_dev_is_serial(dev); - if (dev->puts && serial_only == is_serial) - dev->puts(dev, s); + if (serial_only == is_serial) { + sdev = dev; + if (CONFIG_IS_ENABLED(CONSOLE_PUTSN) && sdev->putsn) + sdev->putsn(sdev, s, len); + else if (sdev->puts) + sdev->puts(sdev, s); + } } } -void err_puts(bool serial_only, const char *s) +/** + * console_puts_select() - Output a string to all console devices + * + * @file: File number to output to (e,g, stdout, see stdio.h) + * @serial_only: true to output only to serial, false to output to everything + * else + * @s: String to output + */ +static void console_puts_select(int file, bool serial_only, const char *s) +{ + console_putsn_select(file, serial_only, s, strlen(s)); +} + +void err_putsn(bool serial_only, const char *s, int len) { if (gd->flags & GD_FLG_DEVINIT) - console_puts_select(stderr, serial_only, s); + console_putsn_select(stderr, serial_only, s, len); +} + +void err_puts(bool serial_only, const char *s) +{ + err_putsn(serial_only, s, strlen(s)); } int err_printf(bool serial_only, const char *fmt, ...) @@ -330,7 +356,7 @@ int err_printf(bool serial_only, const char *fmt, ...) */ ret = vscnprintf(buf, sizeof(buf), fmt, args); va_end(args); - err_puts(serial_only, buf); + err_putsn(serial_only, buf, ret); return ret; } @@ -472,11 +498,24 @@ static inline void console_putc_pager(int file, const char c) stdio_devices[file]->putc(stdio_devices[file], c); } -void console_puts_select(int file, bool serial_only, const char *s) +static inline void console_putsn_select(int file, bool serial_only, + const char *s, int len) { + struct stdio_dev *sdev; + if ((gd->flags & GD_FLG_DEVINIT) && - serial_only == console_dev_is_serial(stdio_devices[file])) - stdio_devices[file]->puts(stdio_devices[file], s); + serial_only == console_dev_is_serial(stdio_devices[file])) { + sdev = stdio_devices[file]; + if (CONFIG_IS_ENABLED(CONSOLE_PUTSN) && sdev->putsn) + sdev->putsn(sdev, s, len); + else + sdev->puts(sdev, s); + } +} + +void console_puts_select(int file, bool serial_only, const char *s) +{ + console_putsn_select(file, serial_only, s, strlen(s)); } static inline void console_puts_pager(int file, const char *s) diff --git a/include/console.h b/include/console.h index 83b8d4f642c..486131f8bf7 100644 --- a/include/console.h +++ b/include/console.h @@ -158,6 +158,21 @@ static inline bool console_record_isempty(void) */ int console_announce_r(void); +/** + * err_putsn() - Output a string with length to selected console devices + * + * This writes to stderr only. It is useful for outputting errors when the + * length is already known. + * + * Outputs exactly @len characters from @s, regardless of any nul characters. + * + * @serial_only: true to output only to serial, false to output to everything + * else + * @s: String to output (need not be nul-terminated) + * @len: Number of characters to output + */ +void err_putsn(bool serial_only, const char *s, int len); + /** * err_puts() - Output a string to selected console devices * -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add the ulib_putsn() function to the U-Boot library interface for standalone applications. This provides length-based string output without requiring nul-termination. The implementation outputs characters one at a time using the exported putc() function, making it available to standalone applications without requiring changes to the jump table. Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/u-boot-lib.h | 15 +++++++++++++++ lib/ulib/ulib.c | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/u-boot-lib.h b/include/u-boot-lib.h index 934cc33eff5..034ff12a344 100644 --- a/include/u-boot-lib.h +++ b/include/u-boot-lib.h @@ -38,4 +38,19 @@ void ulib_uninit(void); */ const char *ulib_get_version(void); +/** + * ulib_putsn() - Write 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. + * + * If CONFIG_CONSOLE_PUTSN is enabled, this calls putsn() directly. + * Otherwise, it outputs characters one at a time using putc(). + * + * @s: String to output (need not be nul-terminated) + * @len: Number of characters to output + */ +void ulib_putsn(const char *s, int len); + #endif diff --git a/lib/ulib/ulib.c b/lib/ulib/ulib.c index c957ae6ba67..bb2cbc7cbac 100644 --- a/lib/ulib/ulib.c +++ b/lib/ulib/ulib.c @@ -6,6 +6,7 @@ * Written by Simon Glass <simon.glass@canonical.com> */ +#include <stdio.h> #include <string.h> #include <version.h> #include <asm/global_data.h> @@ -36,3 +37,12 @@ const char *ulib_get_version(void) { return version_string; } + +void ulib_putsn(const char *s, int len) +{ + if (CONFIG_IS_ENABLED(CONSOLE_PUTSN)) + putsn(s, len); + else + while (len--) + putc(*s++); +} -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The python_check_template, used by the 'Check make check', 'Check make qcheck' and 'Check make tcheck' jobs, does not collect test artifacts. This means that when a test fails, the test-log.html file is not available for debugging. Add an after_script to copy the html/css files from the build directories, and an artifacts section to save them, matching what the buildman_and_testpy_template already does. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9cbadb43a85..509a2956ecb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -418,6 +418,15 @@ Examples: # Set environment variables - export TOOLPATH="--toolpath ${pwd}/build-sandbox/tools --toolpath /opt/coreboot" - export PATH=/opt/qemu/bin:${PATH} + after_script: + - cp -v build-*/*.{html,css} . 2>/dev/null || true + - rm -rf /tmp/venv + artifacts: + when: always + paths: + - "*.html" + - "*.css" + expire_in: 1 week Check make check: extends: .python_check_template -- 2.43.0
participants (1)
-
Simon Glass