[PATCH 00/16] Introduce a pager for the console

From: Simon Glass <sjg@chromium.org> Some U-Boot commands can produce a lot of output and this can run off top of the display. This series introduces a simple pager feature, to allow the output to be read, before pressing SPACE to continue. A fixed buffer size (4K) is used, which sets a limit on the permmited output before paging fails and the output is simply written all in one lot. In most cases this is plenty, but 'env print' does print the whole environment as one string, so if the legacy distro-boot scripts are used it could happen. The default number of visible lines is set with a Kconfig option. Where a video terminal is being used, the page size is set to match that. In general U-Boot cannot guess the number of visible lines, since a serial terminal may be in use. There is also a 'pager' environment variable which can override any detected value. This initial series only counts newlines. It has no support for counting characters so the result will be sub-optimal if very long lines are written. This could be addressed in a future patch. Future work may detect the terminal size by sending an ANSI sequence. Simon Glass (16): console: Add the basic pager implementation console: Add tests for the basic functionality console: Provide a way to output without the pager console: Rename console-put functions to prepare for pager console: Plumb in the pager console: test: Allow tests to bypass the pager console: Support paging with single characters console: Add a few more paging tests console: Reset the pager when entering a new command console: Implement an environment var to control the pager console: Get the pager lines from the vidconsole efi: Enable the console pager for the app doc: Remove obsolete text in README.console doc: Move console docs to rST doc: Tidy up the console docs a little pager: doc: Add mention of the pager feature common/Kconfig | 20 ++ common/Makefile | 2 + common/cli_readline.c | 13 +- common/console.c | 99 +++-- common/pager.c | 208 +++++++++++ doc/README.console | 100 ------ doc/usage/console.rst | 78 ++++ doc/usage/environment.rst | 8 + doc/usage/index.rst | 1 + include/asm-generic/global_data.h | 11 + include/env_callback.h | 7 + include/pager.h | 178 +++++++++ lib/efi_client/Kconfig | 1 + test/common/Makefile | 1 + test/common/pager.c | 576 ++++++++++++++++++++++++++++++ test/test-main.c | 3 + 16 files changed, 1183 insertions(+), 123 deletions(-) create mode 100644 common/pager.c delete mode 100644 doc/README.console create mode 100644 doc/usage/console.rst create mode 100644 include/pager.h create mode 100644 test/common/pager.c -- 2.43.0 base-commit: 96d39714e407ff11177434b835ab1a7d46c8c695 branch: cli

From: Simon Glass <sjg@chromium.org> Add an implementation of the pager. It has quite a simple API: - call pager_post() to send some output, which returns what should actually be sent to the console devices - then call pager_next() repeatedly, outputting what it returns, until it returns NULL There is one special case: pager_next() returns PAGER_WAITING if it is waiting for the user to press a key. In that case, the caller should read a key and then pass it to the next pager_next() call. Internally, there is a simple state machine to cope with hitting the limit (and outputing a prompt), waiting for the user and the clearing the prompt, before continuing in the normal PAGEST_OK state. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/Kconfig | 11 ++++ common/Makefile | 2 + common/pager.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++ include/pager.h | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 common/pager.c create mode 100644 include/pager.h diff --git a/common/Kconfig b/common/Kconfig index 0db3b6babca..048530adff3 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -332,6 +332,17 @@ config SYS_DEVICE_NULLDEV operation of the console by setting stdout to "nulldev". Enable this to use a serial console under board control. +config CONSOLE_PAGER + bool "Enable console output paging" + depends on CONSOLE_MUX + default y if SANDBOX + help + Enable pager functionality for console output. When enabled, long + output will be paused after a configurable number of lines, waiting + for user input (SPACE) to continue. The number of lines per page is + controlled by the 'pager' environment variable. If the variable is + not set or is empty, paging is disabled. + endmenu menu "Logging" diff --git a/common/Makefile b/common/Makefile index 048e4a6b3e2..7270af457f5 100644 --- a/common/Makefile +++ b/common/Makefile @@ -26,6 +26,8 @@ obj-$(CONFIG_MII) += miiphyutil.o obj-$(CONFIG_CMD_MII) += miiphyutil.o obj-$(CONFIG_PHYLIB) += miiphyutil.o +obj-$(CONFIG_CONSOLE_PAGER) += pager.o + obj-$(CONFIG_USB_HOST) += usb.o usb_hub.o obj-$(CONFIG_USB_GADGET) += usb.o obj-$(CONFIG_USB_STORAGE) += usb_storage.o diff --git a/common/pager.c b/common/pager.c new file mode 100644 index 00000000000..084c741989f --- /dev/null +++ b/common/pager.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Deals with splitting up text output into separate screenfuls + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_CONSOLE + +#include <errno.h> +#include <malloc.h> +#include <pager.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +const char *pager_post(struct pager *pag, const char *s) +{ + struct membuf old; + int ret, len; + + if (!pag) + return s; + + len = strlen(s); + if (!len) + return NULL; + + old = pag->mb; + ret = membuf_put(&pag->mb, s, len); + if (ret == len) { + /* all is well */ + } else { + /* + * We couldn't store any of the text, so we'll store none of + * it. The pager is now in an non-functional state until it + * can eject the overflow text. + * + * The buffer is presumably empty, since callers are not allowed + * to call pager_post() unless all the output from the previous + * call was provided via pager_next(). + */ + pag->overflow = s; + pag->mb = old; + } + + return pager_next(pag, 0); +} + +const char *pager_next(struct pager *pag, int key) +{ + char *str, *p, *end; + int ret; + + /* replace the real character we overwrite with nul, if needed */ + if (pag->nulch) { + *pag->nulch = pag->oldch; + pag->nulch = NULL; + } + + /* if we're at the limit, wait */ + switch (pag->state) { + case PAGERST_OK: + break; + case PAGERST_AT_LIMIT: + pag->state = PAGERST_WAIT_USER; + return "\n: Press SPACE to continue"; + case PAGERST_WAIT_USER: + if (key != ' ') + return PAGER_WAITING; + pag->state = PAGERST_CLEAR_PROMPT; + return "\r \r"; + case PAGERST_CLEAR_PROMPT: + pag->state = PAGERST_OK; + break; + } + + ret = membuf_getraw(&pag->mb, pag->buf.size - 1, false, &str); + if (!ret) { + if (pag->overflow) { + const char *oflow = pag->overflow; + + pag->overflow = NULL; + return oflow; + } + return NULL; + } + + /* return lines until we reach the limit */ + for (p = str, end = str + ret; p < end; p++) { + if (*p == '\n' && ++pag->line_count == pag->page_len - 1) { + /* remember to display the pager message next time */ + pag->state = PAGERST_AT_LIMIT; + pag->line_count = 0; + + /* skip the newline, since our prompt has one */ + p++; + break; + } + } + + /* remove the used bytes from the membuf */ + ret = membuf_getraw(&pag->mb, p - str, true, &str); + + /* don't output the newline, since our prompt has one */ + if (pag->state == PAGERST_AT_LIMIT) + p--; + + /* terminate the string */ + pag->nulch = p; + pag->oldch = *pag->nulch; + *pag->nulch = '\0'; + + return str; +} + +void pager_uninit(struct pager *pag) +{ + abuf_uninit(&pag->buf); + free(pag); +} + +int pager_init(struct pager **pagp, int page_len, int buf_size) +{ + struct pager *pag; + + pag = malloc(sizeof(struct pager)); + if (!pag) + return log_msg_ret("pag", -ENOMEM); + memset(pag, '\0', sizeof(struct pager)); + pag->page_len = page_len; + if (!abuf_init_size(&pag->buf, buf_size)) + return log_msg_ret("pah", -ENOMEM); + + /* + * nul-terminate the buffer, which will come in handy if we need to + * return up to the last byte + */ + ((char *)pag->buf.data)[buf_size - 1] = '\0'; + membuf_init(&pag->mb, pag->buf.data, buf_size); + *pagp = pag; + + return 0; +} diff --git a/include/pager.h b/include/pager.h new file mode 100644 index 00000000000..16739d41119 --- /dev/null +++ b/include/pager.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Deals with splitting up text output into separate screenfuls + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#ifndef __PAGER_H +#define __PAGER_H + +#include <stdbool.h> +#include <abuf.h> +#include <membuf.h> +#include <linux/sizes.h> + +#define PAGER_BUF_SIZE SZ_4K + +/* Special return value from pager_next() indicating it's waiting for user input */ +#define PAGER_WAITING ((const char *)1) + +/** + * enum pager_state: Tracks the state of the pager + * + * @PAGERST_OK: Normal output is happening + * @PAGERST_AT_LIMIT: No more output can be provided; the next call to + * pager_next() will return a user prompt + * @PAGERST_WAIT_USER: Waiting for the user to press a key + * @PAGERST_CLEAR_PROMPT: Clearing the prompt ready for more output + */ +enum pager_state { + PAGERST_OK, + PAGERST_AT_LIMIT, + PAGERST_WAIT_USER, + PAGERST_CLEAR_PROMPT, +}; + +/** + * struct pager - pager state + * + * The pager uses a buffer @buf to hold text that it is in the process of + * sending out. This helps deal with the stdio puts() interface, which does not + * permit passing a string length, only a string, which means that strings must + * be nul-terminated. The termination is handled automatically by the pager. + * + * If the text passed to pager_post() is too large for @buf then all the next + * will be written at once, without any paging, in the next call to + * pager_next(). + * + * The membuf @mb is only used to feed out text in chunks, with a pager message + * (and a keypress wait) inserted between each chunk. + * + * @line_count: Number of lines output since last pause + * @page_len: Sets the height of the page in lines. The maximum lines to display + * before pausing is one less than this. Set from 'pager' env variable + * @buf: Buffer containing text to eventually be returned + * @mb: Circular buffer to manage @buf + * @overflow: pointer to overflow text to send nexts + * @nulch: pointer to where a nul character was written, NULL if none + * @oldch: old character that was at @nulch + */ +struct pager { + int line_count; + int page_len; + struct abuf buf; + struct membuf mb; + const char *overflow; + char *nulch; + int oldch; + enum pager_state state; +}; + +#if CONFIG_IS_ENABLED(CONSOLE_PAGER) + +/** + * pager_post() - Add text to the input buffer for later handling + * + * The text is added to the pager buffer and fed out a screenful + * at a time. This function calls pager_post() after storing the text. + * + * After calling pager_post(), if it returns anything other than NULL, you must + * repeatedly call pager_next() until it returns NULL, otherwise text may be + * lost + * + * If @pag is NULL, this does nothing but return @s + * + * @pag: Pager to use, may be NULL + * @s: Text to add + * Return: text which should be sent to output, or NULL if there is no more. + */ +const char *pager_post(struct pager *pag, const char *s); + +/** + * pager_next() - Returns the next screenful of text to show + * + * If this function returns PAGER_WAITING then the caller must check for user + * input and pass in the keypress in the next call to pager_next(). It can + * busy-wait for a keypress, if desired, since pager_next() will only ever + * return PAGER_WAITING until @ch is non-zero. + * + * @pag: Pager to use + * @ch: Key that the user has pressed, or 0 if none + * + * Return: text which should be sent to output, or PAGER_WAITING if waiting for + * the user to press a key, or NULL if there is no more text. + */ +const char *pager_next(struct pager *pag, int ch); + +/** + * pager_uninit() - Uninit the pager + * + * Frees all memory and also @pag + * + * @pag: Pager to uninit + */ +void pager_uninit(struct pager *pag); + +#else +static inline const char *pager_post(struct pager *pag, const char *s) +{ + return s; +} + +static inline const char *pager_next(struct pager *pag, int ch) +{ + return NULL; +} + +#endif + +/** + * pager_init() - Set up a new pager + * + * @pagp: Returns allocaed pager, on success + * @pagelen: Number of lines per page + * @buf_size: Buffer size to use in bytes, this is the maximum amount of output + * that can be paged + * Return: 0 if OK, -ENOMEM if out of memory + */ +int pager_init(struct pager **pagp, int page_len, int buf_size); + +#endif -- 2.43.0

From: Simon Glass <sjg@chromium.org> Cover the various cases in the base code. Put the tests in the 'common' suite to match where the pager implementation is. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- test/common/Makefile | 1 + test/common/pager.c | 324 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 test/common/pager.c diff --git a/test/common/Makefile b/test/common/Makefile index baefc7b3622..ea1317c41dc 100644 --- a/test/common/Makefile +++ b/test/common/Makefile @@ -10,4 +10,5 @@ endif obj-$(CONFIG_CYCLIC) += cyclic.o obj-$(CONFIG_EVENT_DYNAMIC) += event.o obj-y += cread.o +obj-$(CONFIG_CONSOLE_PAGER) += pager.o obj-$(CONFIG_$(PHASE_)CMDLINE) += print.o diff --git a/test/common/pager.c b/test/common/pager.c new file mode 100644 index 00000000000..37796494f79 --- /dev/null +++ b/test/common/pager.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2025 Simon Glass <sjg@chromium.org> + * + * Test for pager functionality + */ + +#include <errno.h> +#include <malloc.h> +#include <pager.h> +#include <string.h> +#include <test/common.h> +#include <test/test.h> +#include <test/ut.h> + +/* Test basic pager init and cleanup */ +static int pager_test_basic_init(struct unit_test_state *uts) +{ + struct pager *pag; + + /* Test successful init */ + ut_assertok(pager_init(&pag, 20, 1024)); + ut_assertnonnull(pag); + ut_asserteq(20, pag->page_len); + ut_asserteq(0, pag->line_count); + ut_assertnull(pag->overflow); + ut_assertnull(pag->nulch); + + /* Clean up */ + pager_uninit(pag); + + /* Test init with different parameters */ + ut_assertok(pager_init(&pag, 10, 2048)); + ut_assertnonnull(pag); + ut_asserteq(10, pag->page_len); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_basic_init, 0); + +/* Test pager with simple text */ +static int pager_test_simple_text(struct unit_test_state *uts) +{ + struct pager *pag; + const char *text = "Hello, World!"; + const char *result; + + ut_assertok(pager_init(&pag, 20, 1024)); + + /* Post some text and get it back */ + result = pager_post(pag, text); + ut_assertnonnull(result); + ut_asserteq_str(text, result); + + /* Should be no more text */ + result = pager_next(pag, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_simple_text, 0); + +/* Test pager with multiple lines */ +static int pager_test_multiline(struct unit_test_state *uts) +{ + struct pager *pag; + const char *text1 = "Line 1\n"; + const char *text2 = "Line 2\n"; + const char *text3 = "Line 3\n"; + const char *result; + + ut_assertok(pager_init(&pag, 20, 1024)); + + /* Post multiple pieces of text */ + result = pager_post(pag, text1); + ut_assertnonnull(result); + ut_asserteq_str(text1, result); + + /* Should be no more text after first post */ + result = pager_next(pag, 0); + ut_assertnull(result); + + result = pager_post(pag, text2); + ut_assertnonnull(result); + ut_asserteq_str(text2, result); + + /* Should be no more text after second post */ + result = pager_next(pag, 0); + ut_assertnull(result); + + result = pager_post(pag, text3); + ut_assertnonnull(result); + ut_asserteq_str(text3, result); + + /* Should be no more text after third post */ + result = pager_next(pag, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_multiline, 0); + +/* Test pager with large text that fills the buffer */ +static int pager_test_large_text(struct unit_test_state *uts) +{ + struct pager *pag; + const char *result; + + ut_assertok(pager_init(&pag, 20, 16)); /* Small buffer */ + + /* Post large text - should fit in buffer */ + result = pager_post(pag, "this is 16 chars"); + ut_assertnonnull(result); + ut_asserteq_str("this is 16 chars", result); + ut_assertnull(pager_next(pag, 0)); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_large_text, 0); + +/* Test pager overflow handling */ +static int pager_test_overflow(struct unit_test_state *uts) +{ + struct pager *pag; + const char *result; + + ut_assertok(pager_init(&pag, 20, 4)); /* Small buffer */ + + /* send some text which is too long for the buffer */ + result = pager_post(pag, "test1"); + ut_assertnonnull(result); + + /* overflow handling should return the text */ + ut_asserteq_str("test1", result); + ut_assertnull(pager_next(pag, 0)); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_overflow, 0); + +/* Test pager with NULL input */ +static int pager_test_null_input(struct unit_test_state *uts) +{ + const char *result; + + /* Test pager_post with NULL pager */ + result = pager_post(NULL, "test"); + ut_asserteq_str("test", result); + + return 0; +} +COMMON_TEST(pager_test_null_input, 0); + +/* Test pager with empty strings */ +static int pager_test_empty_strings(struct unit_test_state *uts) +{ + struct pager *pag; + const char *result; + + ut_assertok(pager_init(&pag, 20, 1024)); + + /* Post empty string */ + result = pager_post(pag, ""); + ut_assertnull(result); + + /* Should be no more text */ + result = pager_next(pag, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_empty_strings, 0); + +/* Test pager buffer management */ +static int pager_test_buffer_management(struct unit_test_state *uts) +{ + struct pager *pag; + const char *text = "Test buffer management"; + const char *result; + + ut_assertok(pager_init(&pag, 20, 1024)); + + /* Verify buffer is properly inited */ + ut_assertnonnull(pag->buf.data); + ut_asserteq(1024, pag->buf.size); + + /* Post text and verify buffer state */ + result = pager_post(pag, text); + ut_assertnonnull(result); + + /* Verify the buffer contains our text */ + ut_asserteq_str(text, result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_buffer_management, 0); + +/* Test pager with very long single line */ +static int pager_test_long_single_line(struct unit_test_state *uts) +{ + struct pager *pag; + char long_line[1000]; + const char *result; + int i; + + ut_assertok(pager_init(&pag, 20, 1024)); + + /* Create a very long line without newlines */ + for (i = 0; i < sizeof(long_line) - 1; i++) + long_line[i] = 'X'; + long_line[sizeof(long_line) - 1] = '\0'; + + /* Post the long line */ + result = pager_post(pag, long_line); + ut_assertnonnull(result); + + /* Should get our text back */ + ut_asserteq_str(long_line, result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_long_single_line, 0); + +/* Test pager line counting and page breaks */ +static int pager_test_line_counting(struct unit_test_state *uts) +{ + struct pager *pag; + const char *multiline_text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n"; + const char *result; + + /* Init with page length of 4 lines */ + ut_assertok(pager_init(&pag, 4, 1024)); + + /* Post multiline text */ + result = pager_post(pag, multiline_text); + ut_assertnonnull(result); + + /* Should get first 3 lines (excluding the 3rd newline) */ + ut_asserteq_str("Line 1\nLine 2\nLine 3", result); + /* line_count is reset to 0 when page limit is reached */ + ut_asserteq(0, pag->line_count); + + /* Next call should return pager prompt */ + result = pager_next(pag, 0); + ut_assertnonnull(result); + ut_asserteq_str("\n: Press SPACE to continue", result); + + /* Press space to continue */ + result = pager_next(pag, ' '); + ut_assertnonnull(result); + ut_asserteq_str("\r \r", result); + + /* Get remaining lines */ + result = pager_next(pag, 0); + ut_assertnonnull(result); + ut_asserteq_str("Line 4\nLine 5\n", result); + + /* Should be no more text */ + result = pager_next(pag, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_line_counting, 0); + +/* Test that PAGER_WAITING is returned when pager waits for user input */ +static int pager_test_pager_waiting(struct unit_test_state *uts) +{ + struct pager *pag; + const char *result; + + /* Create pager with small page size to trigger waiting quickly */ + ut_assertok(pager_init(&pag, 3, 1024)); + + /* Post text that fills exactly the page limit */ + result = pager_post(pag, "Line 1\nLine 2\n"); + ut_assertnonnull(result); + ut_asserteq_str("Line 1\nLine 2", result); + + /* Next call should return the prompt */ + result = pager_next(pag, 0); + ut_assertnonnull(result); + ut_asserteq_str("\n: Press SPACE to continue", result); + + /* Next call without space key should return PAGER_WAITING */ + result = pager_next(pag, 0); + ut_asserteq_ptr(PAGER_WAITING, result); + + /* Another call without space should still return PAGER_WAITING */ + result = pager_next(pag, 'x'); /* Wrong key */ + ut_asserteq_ptr(PAGER_WAITING, result); + + /* Pressing space should clear the prompt */ + result = pager_next(pag, ' '); + ut_assertnonnull(result); + ut_asserteq_str("\r \r", result); + + /* Now should return NULL (no more content) */ + result = pager_next(pag, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_pager_waiting, 0); -- 2.43.0

From: Simon Glass <sjg@chromium.org> Sometimes output should be sent ignoring the pager, such as when it is a message related to paging. Add a parameter to support this. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- common/pager.c | 11 ++-- include/pager.h | 15 ++++-- test/common/pager.c | 123 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 112 insertions(+), 37 deletions(-) diff --git a/common/pager.c b/common/pager.c index 084c741989f..20aa8558654 100644 --- a/common/pager.c +++ b/common/pager.c @@ -14,12 +14,12 @@ DECLARE_GLOBAL_DATA_PTR; -const char *pager_post(struct pager *pag, const char *s) +const char *pager_post(struct pager *pag, bool use_pager, const char *s) { struct membuf old; int ret, len; - if (!pag) + if (!pag || !use_pager) return s; len = strlen(s); @@ -44,14 +44,17 @@ const char *pager_post(struct pager *pag, const char *s) pag->mb = old; } - return pager_next(pag, 0); + return pager_next(pag, true, 0); } -const char *pager_next(struct pager *pag, int key) +const char *pager_next(struct pager *pag, bool use_pager, int key) { char *str, *p, *end; int ret; + if (!use_pager) + return NULL; + /* replace the real character we overwrite with nul, if needed */ if (pag->nulch) { *pag->nulch = pag->oldch; diff --git a/include/pager.h b/include/pager.h index 16739d41119..7f7df690d7f 100644 --- a/include/pager.h +++ b/include/pager.h @@ -74,7 +74,7 @@ struct pager { /** * pager_post() - Add text to the input buffer for later handling * - * The text is added to the pager buffer and fed out a screenful + * If @use_pager the text is added to the pager buffer and fed out a screenful * at a time. This function calls pager_post() after storing the text. * * After calling pager_post(), if it returns anything other than NULL, you must @@ -84,10 +84,12 @@ struct pager { * If @pag is NULL, this does nothing but return @s * * @pag: Pager to use, may be NULL + * @use_pager: Whether or not to use the pager functionality * @s: Text to add * Return: text which should be sent to output, or NULL if there is no more. + * If !@use_pager this just returns @s and does not affect the pager state */ -const char *pager_post(struct pager *pag, const char *s); +const char *pager_post(struct pager *pag, bool use_pager, const char *s); /** * pager_next() - Returns the next screenful of text to show @@ -98,12 +100,14 @@ const char *pager_post(struct pager *pag, const char *s); * return PAGER_WAITING until @ch is non-zero. * * @pag: Pager to use + * @use_pager: Whether or not to use the pager functionality * @ch: Key that the user has pressed, or 0 if none * * Return: text which should be sent to output, or PAGER_WAITING if waiting for * the user to press a key, or NULL if there is no more text. + * If !@use_pager this just returns NULL and does not affect the pager state */ -const char *pager_next(struct pager *pag, int ch); +const char *pager_next(struct pager *pag, bool use_pager, int ch); /** * pager_uninit() - Uninit the pager @@ -115,12 +119,13 @@ const char *pager_next(struct pager *pag, int ch); void pager_uninit(struct pager *pag); #else -static inline const char *pager_post(struct pager *pag, const char *s) +static inline const char *pager_post(struct pager *pag, bool use_pager, + const char *s) { return s; } -static inline const char *pager_next(struct pager *pag, int ch) +static inline const char *pager_next(struct pager *pag, bool use_pager, int ch) { return NULL; } diff --git a/test/common/pager.c b/test/common/pager.c index 37796494f79..f72d0f40304 100644 --- a/test/common/pager.c +++ b/test/common/pager.c @@ -50,12 +50,12 @@ static int pager_test_simple_text(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 20, 1024)); /* Post some text and get it back */ - result = pager_post(pag, text); + result = pager_post(pag, true, text); ut_assertnonnull(result); ut_asserteq_str(text, result); /* Should be no more text */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); pager_uninit(pag); @@ -76,28 +76,28 @@ static int pager_test_multiline(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 20, 1024)); /* Post multiple pieces of text */ - result = pager_post(pag, text1); + result = pager_post(pag, true, text1); ut_assertnonnull(result); ut_asserteq_str(text1, result); /* Should be no more text after first post */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); - result = pager_post(pag, text2); + result = pager_post(pag, true, text2); ut_assertnonnull(result); ut_asserteq_str(text2, result); /* Should be no more text after second post */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); - result = pager_post(pag, text3); + result = pager_post(pag, true, text3); ut_assertnonnull(result); ut_asserteq_str(text3, result); /* Should be no more text after third post */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); pager_uninit(pag); @@ -115,10 +115,10 @@ static int pager_test_large_text(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 20, 16)); /* Small buffer */ /* Post large text - should fit in buffer */ - result = pager_post(pag, "this is 16 chars"); + result = pager_post(pag, true, "this is 16 chars"); ut_assertnonnull(result); ut_asserteq_str("this is 16 chars", result); - ut_assertnull(pager_next(pag, 0)); + ut_assertnull(pager_next(pag, true, 0)); pager_uninit(pag); @@ -135,12 +135,12 @@ static int pager_test_overflow(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 20, 4)); /* Small buffer */ /* send some text which is too long for the buffer */ - result = pager_post(pag, "test1"); + result = pager_post(pag, true, "test1"); ut_assertnonnull(result); /* overflow handling should return the text */ ut_asserteq_str("test1", result); - ut_assertnull(pager_next(pag, 0)); + ut_assertnull(pager_next(pag, true, 0)); pager_uninit(pag); @@ -154,7 +154,7 @@ static int pager_test_null_input(struct unit_test_state *uts) const char *result; /* Test pager_post with NULL pager */ - result = pager_post(NULL, "test"); + result = pager_post(NULL, true, "test"); ut_asserteq_str("test", result); return 0; @@ -170,11 +170,11 @@ static int pager_test_empty_strings(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 20, 1024)); /* Post empty string */ - result = pager_post(pag, ""); + result = pager_post(pag, true, ""); ut_assertnull(result); /* Should be no more text */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); pager_uninit(pag); @@ -197,7 +197,7 @@ static int pager_test_buffer_management(struct unit_test_state *uts) ut_asserteq(1024, pag->buf.size); /* Post text and verify buffer state */ - result = pager_post(pag, text); + result = pager_post(pag, true, text); ut_assertnonnull(result); /* Verify the buffer contains our text */ @@ -225,7 +225,7 @@ static int pager_test_long_single_line(struct unit_test_state *uts) long_line[sizeof(long_line) - 1] = '\0'; /* Post the long line */ - result = pager_post(pag, long_line); + result = pager_post(pag, true, long_line); ut_assertnonnull(result); /* Should get our text back */ @@ -248,7 +248,7 @@ static int pager_test_line_counting(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 4, 1024)); /* Post multiline text */ - result = pager_post(pag, multiline_text); + result = pager_post(pag, true, multiline_text); ut_assertnonnull(result); /* Should get first 3 lines (excluding the 3rd newline) */ @@ -257,22 +257,22 @@ static int pager_test_line_counting(struct unit_test_state *uts) ut_asserteq(0, pag->line_count); /* Next call should return pager prompt */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnonnull(result); ut_asserteq_str("\n: Press SPACE to continue", result); /* Press space to continue */ - result = pager_next(pag, ' '); + result = pager_next(pag, true, ' '); ut_assertnonnull(result); ut_asserteq_str("\r \r", result); /* Get remaining lines */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnonnull(result); ut_asserteq_str("Line 4\nLine 5\n", result); /* Should be no more text */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); pager_uninit(pag); @@ -291,30 +291,30 @@ static int pager_test_pager_waiting(struct unit_test_state *uts) ut_assertok(pager_init(&pag, 3, 1024)); /* Post text that fills exactly the page limit */ - result = pager_post(pag, "Line 1\nLine 2\n"); + result = pager_post(pag, true, "Line 1\nLine 2\n"); ut_assertnonnull(result); ut_asserteq_str("Line 1\nLine 2", result); /* Next call should return the prompt */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnonnull(result); ut_asserteq_str("\n: Press SPACE to continue", result); /* Next call without space key should return PAGER_WAITING */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_asserteq_ptr(PAGER_WAITING, result); /* Another call without space should still return PAGER_WAITING */ - result = pager_next(pag, 'x'); /* Wrong key */ + result = pager_next(pag, true, 'x'); /* Wrong key */ ut_asserteq_ptr(PAGER_WAITING, result); /* Pressing space should clear the prompt */ - result = pager_next(pag, ' '); + result = pager_next(pag, true, ' '); ut_assertnonnull(result); ut_asserteq_str("\r \r", result); /* Now should return NULL (no more content) */ - result = pager_next(pag, 0); + result = pager_next(pag, true, 0); ut_assertnull(result); pager_uninit(pag); @@ -322,3 +322,70 @@ static int pager_test_pager_waiting(struct unit_test_state *uts) return 0; } COMMON_TEST(pager_test_pager_waiting, 0); + +/* Test use_pager parameter - output text directly, while buffer is non-empty */ +static int pager_test_use_pager_param(struct unit_test_state *uts) +{ + struct pager *pag; + const char *buffered_text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n"; + const char *direct_text = "This should be written immediately"; + const char *result; + + /* Init with small page length to ensure paging occurs */ + ut_assertok(pager_init(&pag, 3, 1024)); + + /* Post text with use_pager=true - should trigger paging */ + result = pager_post(pag, true, buffered_text); + ut_assertnonnull(result); + /* Should get first 2 lines */ + ut_asserteq_str("Line 1\nLine 2", result); + + /* Now call pager_post with use_pager=false while text is still buffered */ + result = pager_post(pag, false, direct_text); + /* Should get the text immediately, not from buffer */ + ut_asserteq_ptr(direct_text, result); + + /* Call pager_next with use_pager=false - should return NULL */ + result = pager_next(pag, false, 0); + ut_assertnull(result); + + /* Now continue with use_pager=true to get buffered text */ + result = pager_next(pag, true, 0); + ut_assertnonnull(result); + /* Should get the pager prompt */ + ut_asserteq_str("\n: Press SPACE to continue", result); + + /* Press space to continue */ + result = pager_next(pag, true, ' '); + ut_assertnonnull(result); + ut_asserteq_str("\r \r", result); + + /* Get remaining buffered lines - should be next 2 lines due to page limit */ + result = pager_next(pag, true, 0); + ut_assertnonnull(result); + ut_asserteq_str("Line 3\nLine 4", result); + + /* Should get pager prompt again */ + result = pager_next(pag, true, 0); + ut_assertnonnull(result); + ut_asserteq_str("\n: Press SPACE to continue", result); + + /* Press space to continue */ + result = pager_next(pag, true, ' '); + ut_assertnonnull(result); + ut_asserteq_str("\r \r", result); + + /* Get final line */ + result = pager_next(pag, true, 0); + ut_assertnonnull(result); + ut_asserteq_str("Line 5\n", result); + + /* Should be no more text */ + result = pager_next(pag, true, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_use_pager_param, 0); -- 2.43.0

From: Simon Glass <sjg@chromium.org> Rename console_puts() to console_puts_pager() and console_putc() to console_putc_pager() to prepare for implementing a console-pager feature. All normal output goes through the pager. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- common/console.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/console.c b/common/console.c index c6dfed6e201..fdccf156b5a 100644 --- a/common/console.c +++ b/common/console.c @@ -321,7 +321,7 @@ static int console_tstc(int file) return 0; } -static void console_putc(int file, const char c) +static void console_putc_pager(int file, const char c) { int i; struct stdio_dev *dev; @@ -377,7 +377,7 @@ int console_printf_select_stderr(bool serial_only, const char *fmt, ...) return ret; } -static void console_puts(int file, const char *s) +static void console_puts_pager(int file, const char *s) { int i; struct stdio_dev *dev; @@ -433,7 +433,7 @@ static inline int console_tstc(int file) return stdio_devices[file]->tstc(stdio_devices[file]); } -static inline void console_putc(int file, const char c) +static inline void console_putc_pager(int file, const char c) { stdio_devices[file]->putc(stdio_devices[file], c); } @@ -445,7 +445,7 @@ void console_puts_select(int file, bool serial_only, const char *s) stdio_devices[file]->puts(stdio_devices[file], s); } -static inline void console_puts(int file, const char *s) +static inline void console_puts_pager(int file, const char *s) { stdio_devices[file]->puts(stdio_devices[file], s); } @@ -562,13 +562,13 @@ int ftstc(int file) void fputc(int file, const char c) { if ((unsigned int)file < MAX_FILES) - console_putc(file, c); + console_putc_pager(file, c); } void fputs(int file, const char *s) { if ((unsigned int)file < MAX_FILES) - console_puts(file, s); + console_puts_pager(file, s); } #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT -- 2.43.0

From: Simon Glass <sjg@chromium.org> Send all console output via the pager. If it is disabled, it will do nothing. The pager is only supported if CONSOLE_MUX and SYS_CONSOLE_IS_IN_ENV are enabled. This is the common case for more richly featured boards, i.e. those that can cope with the extra code size of this feature. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- common/Kconfig | 9 ++++++ common/console.c | 52 +++++++++++++++++++++++++------ include/asm-generic/global_data.h | 11 +++++++ 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/common/Kconfig b/common/Kconfig index 048530adff3..6c54f90109c 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -343,6 +343,15 @@ config CONSOLE_PAGER controlled by the 'pager' environment variable. If the variable is not set or is empty, paging is disabled. +config CONSOLE_PAGER_LINES + int "Number of lines per page" + depends on CONSOLE_PAGER + default 25 + help + Sets the default number of lines that the pager assumes is visible on + the display. This is used as a default if the "pager" environment + variable is unset. + endmenu menu "Logging" diff --git a/common/console.c b/common/console.c index fdccf156b5a..9bc506f0464 100644 --- a/common/console.c +++ b/common/console.c @@ -16,6 +16,7 @@ #include <malloc.h> #include <mapmem.h> #include <os.h> +#include <pager.h> #include <serial.h> #include <stdio_dev.h> #include <exports.h> @@ -377,17 +378,32 @@ int console_printf_select_stderr(bool serial_only, const char *fmt, ...) return ret; } -static void console_puts_pager(int file, const char *s) +static void console_puts(int file, bool use_pager, const char *s) { - int i; - struct stdio_dev *dev; + int key = 0; - for_each_console_dev(i, file, dev) { - if (dev->puts != NULL) - dev->puts(dev, s); + for (s = pager_post(gd_pager(), use_pager, s); s; + s = pager_next(gd_pager(), use_pager, key)) { + struct stdio_dev *dev; + int i; + + key = 0; + if (s == PAGER_WAITING) { + key = getchar(); + } else if (*s) { + for_each_console_dev(i, file, dev) { + if (dev->puts != NULL) + dev->puts(dev, s); + } + } } } +static void console_puts_pager(int file, const char *s) +{ + console_puts(file, true, s); +} + #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT static void console_flush(int file) { @@ -407,7 +423,8 @@ static inline void console_doenv(int file, struct stdio_dev *dev) iomux_doenv(file, dev->name); } #endif -#else + +#else /* !CONSOLE_MUX */ static void console_devices_set(int file, struct stdio_dev *dev) { @@ -780,8 +797,8 @@ void puts(const char *s) return pre_console_puts(s); if (gd->flags & GD_FLG_DEVINIT) { - /* Send to the standard output */ - fputs(stdout, s); + /* Send to the standard output through pager system */ + console_puts_pager(stdout, s); } else { /* Send directly to the handler */ pre_console_puts(s); @@ -1101,6 +1118,21 @@ static void stdio_print_current_devices(void) printf("%s\n", stderrname); } +static void setup_pager(void) +{ + /* Init pager now that console is ready */ + if (IS_ENABLED(CONFIG_CONSOLE_PAGER)) { + int lines = IF_ENABLED_INT(CONFIG_CONSOLE_PAGER, + CONFIG_CONSOLE_PAGER_LINES); + int ret; + + ret = pager_init(gd_pagerp(), env_get_hex("pager", lines), + PAGER_BUF_SIZE); + if (ret) + printf("Failed to init pager\n"); + } +} + #if CONFIG_IS_ENABLED(SYS_CONSOLE_IS_IN_ENV) /* Called after the relocation - use desired console functions */ int console_init_r(void) @@ -1185,6 +1217,7 @@ done: } gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */ + setup_pager(); print_pre_console_buffer(flushpoint); return 0; @@ -1252,6 +1285,7 @@ int console_init_r(void) } gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */ + setup_pager(); print_pre_console_buffer(flushpoint); return 0; diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h index 464b9c3eee1..0157fead337 100644 --- a/include/asm-generic/global_data.h +++ b/include/asm-generic/global_data.h @@ -477,6 +477,9 @@ struct global_data { */ struct upl *upl; #endif +#if CONFIG_IS_ENABLED(CONSOLE_PAGER) + struct pager *pager; +#endif }; #ifndef DO_DEPS_ONLY static_assert(sizeof(struct global_data) == GD_SIZE); @@ -618,6 +621,14 @@ static_assert(sizeof(struct global_data) == GD_SIZE); #define gd_passage_dtb() 0 #endif +#if CONFIG_IS_ENABLED(CONSOLE_PAGER) +#define gd_pager() gd->pager +#define gd_pagerp() &gd->pager +#else +#define gd_pager() NULL +#define gd_pagerp() NULL +#endif + /** * enum gd_flags - global data flags * -- 2.43.0

From: Simon Glass <sjg@chromium.org> We generally don't want the pager to be active when running tests, since U-Boot appears to hang forever. Perhaps we could detect when the tests are being run interactively and use the pager in that case. But for now, just bypass it. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- common/pager.c | 20 +++++++++++++++++++- include/pager.h | 19 +++++++++++++++++++ test/common/pager.c | 36 ++++++++++++++++++++++++++++++++++++ test/test-main.c | 3 +++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/common/pager.c b/common/pager.c index 20aa8558654..a7ad77bea3b 100644 --- a/common/pager.c +++ b/common/pager.c @@ -19,7 +19,7 @@ const char *pager_post(struct pager *pag, bool use_pager, const char *s) struct membuf old; int ret, len; - if (!pag || !use_pager) + if (!pag || !use_pager || pag->state == PAGERST_TEST_BYPASS) return s; len = strlen(s); @@ -76,6 +76,8 @@ const char *pager_next(struct pager *pag, bool use_pager, int key) case PAGERST_CLEAR_PROMPT: pag->state = PAGERST_OK; break; + case PAGERST_TEST_BYPASS: + return NULL; } ret = membuf_getraw(&pag->mb, pag->buf.size - 1, false, &str); @@ -123,6 +125,22 @@ void pager_uninit(struct pager *pag) free(pag); } +bool pager_set_bypass(struct pager *pag, bool bypass) +{ + bool was_bypassed = false; + + if (!pag) + return false; + was_bypassed = pag->state == PAGERST_TEST_BYPASS; + + if (bypass) + pag->state = PAGERST_TEST_BYPASS; + else + pag->state = PAGERST_OK; + + return was_bypassed; +} + int pager_init(struct pager **pagp, int page_len, int buf_size) { struct pager *pag; diff --git a/include/pager.h b/include/pager.h index 7f7df690d7f..9bd99b5c959 100644 --- a/include/pager.h +++ b/include/pager.h @@ -26,12 +26,14 @@ * pager_next() will return a user prompt * @PAGERST_WAIT_USER: Waiting for the user to press a key * @PAGERST_CLEAR_PROMPT: Clearing the prompt ready for more output + * @PAGERST_TEST_BYPASS: Pager is being bypassed since tests are running */ enum pager_state { PAGERST_OK, PAGERST_AT_LIMIT, PAGERST_WAIT_USER, PAGERST_CLEAR_PROMPT, + PAGERST_TEST_BYPASS, }; /** @@ -109,6 +111,18 @@ const char *pager_post(struct pager *pag, bool use_pager, const char *s); */ const char *pager_next(struct pager *pag, bool use_pager, int ch); +/** + * pager_set_bypass() - put the pager into bypass mode + * + * This is used for tests. Bypass mode stops the pager from doing anything to + * interrupt output + * + * @pag: Pager to use, may be NULL in which case this function does nothing + * @bypass: true to put the pager in bypass mode, false to return to normal mode + * Return: old value of the bypass flag + */ +bool pager_set_bypass(struct pager *pag, bool bypass); + /** * pager_uninit() - Uninit the pager * @@ -130,6 +144,11 @@ static inline const char *pager_next(struct pager *pag, bool use_pager, int ch) return NULL; } +static inline bool pager_set_bypass(struct pager *pag, bool bypass) +{ + return true; +} + #endif /** diff --git a/test/common/pager.c b/test/common/pager.c index f72d0f40304..2512652ea01 100644 --- a/test/common/pager.c +++ b/test/common/pager.c @@ -389,3 +389,39 @@ static int pager_test_use_pager_param(struct unit_test_state *uts) return 0; } COMMON_TEST(pager_test_use_pager_param, 0); + +/* Test pager bypass mode */ +static int pager_test_bypass_mode(struct unit_test_state *uts) +{ + struct pager *pag; + const char *text = "This text should be returned directly"; + const char *result; + + /* Init with small page length to ensure paging would normally occur */ + ut_assertok(pager_init(&pag, 2, 1024)); + + /* Enable bypass mode */ + pager_set_bypass(pag, true); + + /* Post text - should get original string back directly */ + result = pager_post(pag, true, text); + ut_asserteq_ptr(text, result); /* Should be same pointer */ + + /* pager_next should return NULL in bypass mode */ + result = pager_next(pag, true, 0); + ut_assertnull(result); + + /* Disable bypass mode */ + pager_set_bypass(pag, false); + + /* Now pager should work normally */ + result = pager_post(pag, true, text); + ut_assertnonnull(result); + /* In normal mode, result should be different from original text */ + ut_assert(result != text); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_bypass_mode, 0); diff --git a/test/test-main.c b/test/test-main.c index 8515a77fe42..4238bb196eb 100644 --- a/test/test-main.c +++ b/test/test-main.c @@ -14,6 +14,7 @@ #include <net.h> #include <of_live.h> #include <os.h> +#include <pager.h> #include <spl.h> #include <usb.h> #include <dm/ofnode.h> @@ -747,8 +748,10 @@ int ut_run_list(struct unit_test_state *uts, const char *category, memcpy(uts->fdt_copy, gd->fdt_blob, uts->fdt_size); } uts->force_run = force_run; + pager_set_bypass(gd_pager(), true); ret = ut_run_tests(uts, prefix, tests, count, select_name, test_insert); + pager_set_bypass(gd_pager(), false); /* Best efforts only...ignore errors */ if (has_dm_tests) -- 2.43.0

From: Simon Glass <sjg@chromium.org> A single character may result in a stall waiting for user input, which means that it may request that a string be output. So when the pager is active we never actually use the devices' putc() methods. Add a special case so they don't go to wrack and ruin. As before, the pager is only supported with CONFIG_CONSOLE_MUX enabled. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/console.c | 22 +++++++++++++++------- test/common/pager.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/common/console.c b/common/console.c index 9bc506f0464..7bafe98375a 100644 --- a/common/console.c +++ b/common/console.c @@ -322,14 +322,22 @@ static int console_tstc(int file) return 0; } +static void console_puts_pager(int file, const char *s); + static void console_putc_pager(int file, const char c) { - int i; - struct stdio_dev *dev; + if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) { + char str[2] = {c, '\0'}; - for_each_console_dev(i, file, dev) { - if (dev->putc != NULL) - dev->putc(dev, c); + console_puts_pager(file, str); + } else { + int i; + struct stdio_dev *dev; + + for_each_console_dev(i, file, dev) { + if (dev->putc != NULL) + dev->putc(dev, c); + } } } @@ -757,8 +765,8 @@ void putc(const char c) return pre_console_putc(c); if (gd->flags & GD_FLG_DEVINIT) { - /* Send to the standard output */ - fputc(stdout, c); + /* Send to the standard output through pager system */ + console_putc_pager(stdout, c); } else { /* Send directly to the handler */ pre_console_putc(c); diff --git a/test/common/pager.c b/test/common/pager.c index 2512652ea01..c1c2d40f648 100644 --- a/test/common/pager.c +++ b/test/common/pager.c @@ -425,3 +425,43 @@ static int pager_test_bypass_mode(struct unit_test_state *uts) return 0; } COMMON_TEST(pager_test_bypass_mode, 0); + +/* Test that single character output via putc goes through pager */ +static int pager_test_putc(struct unit_test_state *uts) +{ + struct pager *pag; + const char *result; + + /* Init pager */ + ut_assertok(pager_init(&pag, 20, 1024)); + pager_set_bypass(pag, true); + + /* + * Test that individual characters can be posted via pager API + * This verifies that console_putc_pager() routes through the pager + * system + */ + result = pager_post(pag, true, "A"); + ut_asserteq_ptr("A", result); /* Bypass mode returns original pointer */ + + result = pager_post(pag, true, "\n"); + ut_asserteq_ptr("\n", result); + + result = pager_post(pag, true, "B"); + ut_asserteq_ptr("B", result); + + /* Disable bypass to test normal functionality with single chars */ + pager_set_bypass(pag, false); + + result = pager_post(pag, true, "X"); + ut_assertnonnull(result); + ut_asserteq_str("X", result); + + result = pager_next(pag, true, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_putc, 0); -- 2.43.0

From: Simon Glass <sjg@chromium.org> Provide a test that the prompt is displayed and another that paging happens but does not appear in the recorded console-output. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/common/pager.c | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/test/common/pager.c b/test/common/pager.c index c1c2d40f648..792f5f24153 100644 --- a/test/common/pager.c +++ b/test/common/pager.c @@ -12,6 +12,9 @@ #include <test/common.h> #include <test/test.h> #include <test/ut.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; /* Test basic pager init and cleanup */ static int pager_test_basic_init(struct unit_test_state *uts) @@ -465,3 +468,109 @@ static int pager_test_putc(struct unit_test_state *uts) return 0; } COMMON_TEST(pager_test_putc, 0); + +/* Test writing up to page limit then adding final newline */ +static int pager_test_limit_plus_newline(struct unit_test_state *uts) +{ + struct pager *pag; + const char *result; + + /* Init with page length of 3 lines */ + ut_assertok(pager_init(&pag, 3, 1024)); + + /* Write text that reaches exactly the page limit (2 newlines) */ + result = pager_post(pag, true, "Line 1\nLine 2"); + ut_assertnonnull(result); + ut_asserteq_str("Line 1\nLine 2", result); + ut_asserteq(1, pag->line_count); /* Should have 1 line counted */ + + /* Should be no more text yet - haven't hit limit */ + result = pager_next(pag, true, 0); + ut_assertnull(result); + + /* Now post a single newline - this should trigger the page limit */ + result = pager_post(pag, true, "\n"); + ut_assertnonnull(result); + /* + * Should get empty string since we hit the limit and the newline is + * consumed + */ + ut_asserteq_str("", result); + + /* Next call should return the pager prompt since we hit the limit */ + result = pager_next(pag, true, 0); + ut_assertnonnull(result); + ut_asserteq_str("\n: Press SPACE to continue", result); + + /* Press space to continue */ + result = pager_next(pag, true, ' '); + ut_assertnonnull(result); + ut_asserteq_str("\r \r", result); + + /* Should be no more text */ + result = pager_next(pag, true, 0); + ut_assertnull(result); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_limit_plus_newline, 0); + +/* Test console integration - pager prompt appears in console output */ +static int pager_test_console(struct unit_test_state *uts) +{ + struct pager *pag, *orig_pag; + char line[100]; + int avail, ret; + + /* Save original pager */ + orig_pag = gd_pager(); + + /* Create our own pager for testing */ + ret = pager_init(&pag, 2, 1024); + if (ret) { + gd->pager = orig_pag; + return CMD_RET_FAILURE; + } + + /* Set up pager to be one away from limit (1 line already counted) */ + pag->line_count = 1; + + /* Assign our pager to the global data */ + gd->pager = pag; + + /* Trigger paging with a second newline */ + putc('\n'); + + /* Check if there's any console output available at all */ + avail = console_record_avail(); + + /* Restore original pager first */ + gd->pager = orig_pag; + pager_uninit(pag); + + /* Now check what we got */ + if (!avail) { + ut_reportf("No console output was recorded at all"); + return CMD_RET_FAILURE; + } + + /* Try to read the actual output */ + ret = console_record_readline(line, sizeof(line)); + if (ret < 0) { + ut_reportf("Failed to read first line, avail was %d", avail); + return CMD_RET_FAILURE; + } + + /* + * console recording does not see the pager prompt, so we should have + * just got a newline + */ + ut_asserteq_str("", line); + + ut_assert_console_end(); + + return 0; +} +COMMON_TEST(pager_test_console, UTF_CONSOLE); -- 2.43.0

From: Simon Glass <sjg@chromium.org> We don't want the pager appearing when entering commands, so add a way to bypass it. Reset the line count to zero before executing the command. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/cli_readline.c | 13 +++++++++++-- common/pager.c | 15 +++++++++++++++ include/pager.h | 13 +++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/common/cli_readline.c b/common/cli_readline.c index 4e6797a1944..2326e4b4f37 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -13,6 +13,7 @@ #include <command.h> #include <hang.h> #include <malloc.h> +#include <pager.h> #include <time.h> #include <watchdog.h> #include <linux/errno.h> @@ -650,6 +651,9 @@ int cli_readline_into_buffer(const char *const prompt, char *buffer, uint len = CONFIG_SYS_CBSIZE; int rc; static int initted; + bool old_bypass; + + old_bypass = pager_set_bypass(gd_pager(), true); /* * Say N to CMD_HISTORY_USE_CALLOC will skip runtime @@ -673,9 +677,14 @@ int cli_readline_into_buffer(const char *const prompt, char *buffer, puts(prompt); rc = cread_line(prompt, p, &len, timeout); - return rc < 0 ? rc : len; + rc = rc < 0 ? rc : len; } else { - return cread_line_simple(prompt, p); + rc = cread_line_simple(prompt, p); } + + pager_set_bypass(gd_pager(), old_bypass); + pager_reset(gd_pager()); + + return rc; } diff --git a/common/pager.c b/common/pager.c index a7ad77bea3b..69b05dac5cc 100644 --- a/common/pager.c +++ b/common/pager.c @@ -141,6 +141,21 @@ bool pager_set_bypass(struct pager *pag, bool bypass) return was_bypassed; } +void pager_set_page_len(struct pager *pag, int page_len) +{ + if (page_len < 2) + return; + pag->page_len = page_len; + pag->line_count = 0; + if (!page_len) + pag->state = PAGERST_TEST_BYPASS; +} + +void pager_reset(struct pager *pag) +{ + pag->line_count = 0; +} + int pager_init(struct pager **pagp, int page_len, int buf_size) { struct pager *pag; diff --git a/include/pager.h b/include/pager.h index 9bd99b5c959..fde502f3edd 100644 --- a/include/pager.h +++ b/include/pager.h @@ -123,6 +123,15 @@ const char *pager_next(struct pager *pag, bool use_pager, int ch); */ bool pager_set_bypass(struct pager *pag, bool bypass); +/** + * pager_reset() - reset the line count in the pager + * + * Sets line_count to zero so that the pager starts afresh with its counting. + * + * @pag: Pager to update + */ +void pager_reset(struct pager *pag); + /** * pager_uninit() - Uninit the pager * @@ -149,6 +158,10 @@ static inline bool pager_set_bypass(struct pager *pag, bool bypass) return true; } +static void pager_reset(struct pager *pag) +{ +} + #endif /** -- 2.43.0

From: Simon Glass <sjg@chromium.org> The 'pager' environment variable can be used to set the number of lines on the display. Provide a callback for this. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/pager.c | 28 ++++++++++++++++++++++++++++ doc/usage/environment.rst | 3 +++ include/env_callback.h | 7 +++++++ 3 files changed, 38 insertions(+) diff --git a/common/pager.c b/common/pager.c index 69b05dac5cc..fda02c9a6c0 100644 --- a/common/pager.c +++ b/common/pager.c @@ -7,6 +7,7 @@ #define LOG_CATEGORY LOGC_CONSOLE +#include <env.h> #include <errno.h> #include <malloc.h> #include <pager.h> @@ -156,6 +157,33 @@ void pager_reset(struct pager *pag) pag->line_count = 0; } +static int on_pager(const char *name, const char *value, enum env_op op, + int flags) +{ + struct pager *pag = gd_pager(); + int new_page_len; + + if (!IS_ENABLED(CONFIG_CONSOLE_PAGER) || !pag) + return 0; + + switch (op) { + case env_op_create: + case env_op_overwrite: + if (value) { + new_page_len = simple_strtoul(value, NULL, 16); + pager_set_page_len(pag, new_page_len); + } + break; + case env_op_delete: + /* Reset to default when deleted */ + pager_set_page_len(pag, CONFIG_CONSOLE_PAGER_LINES); + break; + } + + return 0; +} +U_BOOT_ENV_CALLBACK(pager, on_pager); + int pager_init(struct pager **pagp, int page_len, int buf_size) { struct pager *pag; diff --git a/doc/usage/environment.rst b/doc/usage/environment.rst index 1ab3ee7ced9..6235d7276f0 100644 --- a/doc/usage/environment.rst +++ b/doc/usage/environment.rst @@ -335,6 +335,9 @@ netretry Useful on scripts which control the retry operation themselves. +pager + Decimal number of visible lines on the display, or serial console. + rng_seed_size Size of random value added to device-tree node /chosen/rng-seed. This variable is given as a decimal number. diff --git a/include/env_callback.h b/include/env_callback.h index bc8ff1923e1..dccf0bd1464 100644 --- a/include/env_callback.h +++ b/include/env_callback.h @@ -75,6 +75,12 @@ #define DFU_CALLBACK #endif +#ifdef CONFIG_CONSOLE_PAGER +#define PAGER_CALLBACK "pager:pager," +#else +#define PAGER_CALLBACK +#endif + /* * This list of callback bindings is static, but may be overridden by defining * a new association in the ".callbacks" environment variable. @@ -88,6 +94,7 @@ DFU_CALLBACK \ "loadaddr:loadaddr," \ SILENT_CALLBACK \ + PAGER_CALLBACK \ "stdin:console,stdout:console,stderr:console," \ "serial#:serialno," \ CFG_ENV_CALLBACK_LIST_STATIC -- 2.43.0

From: Simon Glass <sjg@chromium.org> If there is a video console, use that to set the number of lines for the pager. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/console.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/console.c b/common/console.c index 7bafe98375a..bddbde9de5a 100644 --- a/common/console.c +++ b/common/console.c @@ -1134,6 +1134,21 @@ static void setup_pager(void) CONFIG_CONSOLE_PAGER_LINES); int ret; + /* get number of lines from the video console, if available */ + if (IS_ENABLED(CONFIG_VIDEO)) { + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_VIDEO_CONSOLE, + &dev); + if (!ret) { + struct vidconsole_priv *priv; + + priv = dev_get_uclass_priv(dev); + lines = priv->rows; + } + } + ret = pager_init(gd_pagerp(), env_get_hex("pager", lines), PAGER_BUF_SIZE); if (ret) -- 2.43.0

From: Simon Glass <sjg@chromium.org> The app can produce quite a bit of output, so enable the pager feature. Signed-off-by: Simon Glass <sjg@chromium.org> --- lib/efi_client/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/efi_client/Kconfig b/lib/efi_client/Kconfig index 743181834bf..5726e91e814 100644 --- a/lib/efi_client/Kconfig +++ b/lib/efi_client/Kconfig @@ -22,6 +22,7 @@ config EFI_APP depends on X86 || ARM select CHARSET select EVENT + imply CONSOLE_PAGER help Build U-Boot as an application which can be started from EFI. This is useful for examining a platform in the early stages of porting -- 2.43.0

From: Simon Glass <sjg@chromium.org> The latter part of this file is very out-of-date, so drop it. Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/README.console | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/doc/README.console b/doc/README.console index 9f5812c89d1..debef45760d 100644 --- a/doc/README.console +++ b/doc/README.console @@ -58,43 +58,3 @@ You can use the following functions to access the console: Remember that all FILE-related functions CANNOT be used before U-Boot relocation (done in 'board_init_r' in arch/*/lib/board.c). - -HOW CAN I USE STANDARD FILE INTO APPLICATIONS? ----------------------------------------------- - -Use the 'bd_mon_fnc' field of the bd_info structure passed to the -application to do everything you want with the console. - -But REMEMBER that that will work only if you have not overwritten any -U-Boot code while loading (or uncompressing) the image of your -application. - -For example, you won't get the console stuff running in the Linux -kernel because the kernel overwrites U-Boot before running. Only -some parameters like the framebuffer descriptors are passed to the -kernel in the high memory area to let the applications (the kernel) -use the framebuffers initialized by U-Boot. - -SUPPORTED DRIVERS ------------------ - -Working drivers: - - serial (architecture dependent serial stuff) - video (mpc8xx video controller) - -Work in progress: - - wl_kbd (Wireless 4PPM keyboard) - -Waiting for volounteers: - - lcd (mpc8xx lcd controller; to ) - -TESTED CONFIGURATIONS ---------------------- - -The driver has been tested with the following configurations (see -CREDITS for other contact informations): - -- MPC823FADS with AD7176 on a PAL TV (YCbYCr) - arsenio@tin.it -- 2.43.0

From: Simon Glass <sjg@chromium.org> Move the console readme into the main documentation, with as few changes as possible. Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/{README.console => usage/console.rst} | 13 ++++++------- doc/usage/index.rst | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) rename doc/{README.console => usage/console.rst} (87%) diff --git a/doc/README.console b/doc/usage/console.rst similarity index 87% rename from doc/README.console rename to doc/usage/console.rst index debef45760d..f624cc58038 100644 --- a/doc/README.console +++ b/doc/usage/console.rst @@ -1,11 +1,10 @@ -SPDX-License-Identifier: GPL-2.0+ -/* - * (C) Copyright 2000 - * Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it - */ +.. SPDX-License-Identifier: GPL-2.0+ +.. sectionauthor:: Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it +.. (C) Copyright 2000 +======================= U-Boot console handling -======================== +======================= HOW THE CONSOLE WORKS? ---------------------- @@ -57,4 +56,4 @@ You can use the following functions to access the console: fgetc (like getc but redirected to a file) Remember that all FILE-related functions CANNOT be used before -U-Boot relocation (done in 'board_init_r' in arch/*/lib/board.c). +U-Boot relocation (done in 'board_init_r' in `arch/*/lib/board.c`). diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 20465b8bff6..585aa3f9784 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -6,6 +6,7 @@ Use U-Boot spl_boot blkmap + console dfu environment fdt_overlays -- 2.43.0

From: Simon Glass <sjg@chromium.org> Tweak the documentation to look better when viewed. Use 'write' instead of 'put'. Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/usage/console.rst | 69 ++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/doc/usage/console.rst b/doc/usage/console.rst index f624cc58038..d7d41bca187 100644 --- a/doc/usage/console.rst +++ b/doc/usage/console.rst @@ -6,10 +6,10 @@ U-Boot console handling ======================= -HOW THE CONSOLE WORKS? ----------------------- +Introduction +------------ -At system startup U-Boot initializes a serial console. When U-Boot +At system-startup U-Boot initializes a serial console. When U-Boot relocates itself to RAM, all console drivers are initialized (they will register all detected console devices to the system for further use). @@ -17,43 +17,46 @@ use). If not defined in the environment, the first input device is assigned to the 'stdin' file, the first output one to 'stdout' and 'stderr'. -You can use the command "coninfo" to see all registered console +You can use the command `coninfo` to see all registered console devices and their flags. You can assign a standard file (stdin, stdout or stderr) to any device you see in that list simply by assigning its name to the corresponding environment variable. For -example: +example:: - setenv stdin serial <- To use the serial input - setenv stdout video <- To use the video console + # Use the serial input + setenv stdin serial -Do a simple "saveenv" to save the console settings in the environment + # Use the video console + setenv stdout vidconsole + +Do a simple `saveenv` to save the console settings in the environment and get them working on the next startup, too. -HOW CAN I USE STANDARD FILE INTO THE SOURCES? ---------------------------------------------- +How to output text to the console +--------------------------------- You can use the following functions to access the console: -* STDOUT: - putc (to put a char to stdout) - puts (to put a string to stdout) - printf (to format and put a string to stdout) - -* STDIN: - tstc (to test for the presence of a char in stdin) - getc (to get a char from stdin) - -* STDERR: - eputc (to put a char to stderr) - eputs (to put a string to stderr) - eprintf (to format and put a string to stderr) - -* FILE (can be 'stdin', 'stdout', 'stderr'): - fputc (like putc but redirected to a file) - fputs (like puts but redirected to a file) - fprintf (like printf but redirected to a file) - ftstc (like tstc but redirected to a file) - fgetc (like getc but redirected to a file) - -Remember that all FILE-related functions CANNOT be used before -U-Boot relocation (done in 'board_init_r' in `arch/*/lib/board.c`). +stdout + - putc() - write a char to stdout + - puts() - write a string to stdout + - printf() - format and write a string to stdout + +stdin + - tstc() - test for the presence of a char in stdin + - getchar() - get a char from stdin + +stderr + - eputc() - write a char to stderr + - eputs() - write a string to stderr + - eprintf() - format and write a string to stderr + +file ('stdin', 'stdout' or 'stderr') + - fputc() - write a char to a file + - fputs() - write a string to a file + - fprintf() - format and write a string to a file + - ftstc() - test for the presence of a char in file + - fgetc() - get a char from a file + +Remember that FILE-related functions CANNOT be used before U-Boot relocation, +which is done in `board_init_r()`. -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add a note about the pager to the console documentation. Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/usage/console.rst | 16 ++++++++++++++++ doc/usage/environment.rst | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/doc/usage/console.rst b/doc/usage/console.rst index d7d41bca187..95424c43ee9 100644 --- a/doc/usage/console.rst +++ b/doc/usage/console.rst @@ -60,3 +60,19 @@ file ('stdin', 'stdout' or 'stderr') Remember that FILE-related functions CANNOT be used before U-Boot relocation, which is done in `board_init_r()`. + +Pager +----- + +U-Boot has a simple pager feature, enabled with `CONFIG_CONSOLE_PAGER`. It is +only available if both `CONFIG_CONSOLE_MUX` is enabled. + +When activated, the pager pauses at the end of each 'page' (screenful) of +output, shows a prompt and lets the user read the output. To continue, press +SPACE. + +The number of lines before the pager kicks in is configurable using the `pager` +environment variable, which should contain a decimal value. Set it to 0 (or +leave it unset) to disable the pager. If the variable is not present then the +number of lines in the video console is used. If there is no video console, then +`CONSOLE_PAGER_LINES` sets the number of lines. diff --git a/doc/usage/environment.rst b/doc/usage/environment.rst index 6235d7276f0..696acbfd6a5 100644 --- a/doc/usage/environment.rst +++ b/doc/usage/environment.rst @@ -336,7 +336,12 @@ netretry themselves. pager +<<<<<<< HEAD Decimal number of visible lines on the display, or serial console. +======= + Decimal number of visible lines on the display, or serial console. See + :doc:`/usage/console`. +>>>>>>> 949c148e39e (pager: doc: Add mention of the pager feature) rng_seed_size Size of random value added to device-tree node /chosen/rng-seed. -- 2.43.0
participants (1)
-
Simon Glass