From patchwork Fri Aug 22 14:21:32 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 106 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1755872535; bh=xl91DcKmCIPAfq9GtlZP382LaxEuxqPvFHbR53X19Wo=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=nrVgBfJzHSKPvvGqNFCXAHyKjyLix29UQrQJSVQfXTxXlkJTE+OAlYgtok/EE5cJI 33mfQ7Y5anmNbivP/h9/VfX3D11cVQK8iMJiArYxwNXQn4uysClyaAHP/YeS0Vynye Pp9vauo/HIU2l7bN56fzbwAMbe1F9GxlIEO7MmEZ9wz7NFvabVcNRuvBeykXaAFr9b FM7IEzjJgK1HGadSbQbDuTOD++I8aRAFAtlk7/WuWYTBSIjWlOdIJU9Qhq8q//Ng4o BEHU13v401n9q/dp/8b2NEOHIGrVAlIVU0S7Yqulw+tkOLEeyGpbJgjWcpJGvOgWFR 7WctqVHM+naRQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AB73E676EF for ; Fri, 22 Aug 2025 08:22:15 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 6ApDKUm5Mlwd for ; Fri, 22 Aug 2025 08:22:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1755872535; bh=xl91DcKmCIPAfq9GtlZP382LaxEuxqPvFHbR53X19Wo=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=nrVgBfJzHSKPvvGqNFCXAHyKjyLix29UQrQJSVQfXTxXlkJTE+OAlYgtok/EE5cJI 33mfQ7Y5anmNbivP/h9/VfX3D11cVQK8iMJiArYxwNXQn4uysClyaAHP/YeS0Vynye Pp9vauo/HIU2l7bN56fzbwAMbe1F9GxlIEO7MmEZ9wz7NFvabVcNRuvBeykXaAFr9b FM7IEzjJgK1HGadSbQbDuTOD++I8aRAFAtlk7/WuWYTBSIjWlOdIJU9Qhq8q//Ng4o BEHU13v401n9q/dp/8b2NEOHIGrVAlIVU0S7Yqulw+tkOLEeyGpbJgjWcpJGvOgWFR 7WctqVHM+naRQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9A2E7676DB for ; Fri, 22 Aug 2025 08:22:15 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1755872533; bh=O6Yk3zo6sshGWSV6AomLaDVVCj8P1pRGfjpdwm9fj5Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tJyvIs/7UCIXhTz2NAvES8QEBS4Dn0PMkHiz9/I+BWedGQ85suI1b6hzw+tv97f+5 ur5APgn014vjnPl+0bfO+SKIKSrGefyOMsb/W227X+Ds2dJwsFCH6liZYnhqYrekZq Io0nimPJYgikOvUPbpbNn1/l+Jn/kYmQFfHFD0zNOq33THEriXc1e9iyVo6dSJQ0ar lGN7BgEvBDLcnVGqsScIsr2UluWrOVMjrwyqFRXjn783F4QYzNMGeCOFXjvG19pJO2 aoar74IWblwCjvZpnmcaX9xC2SXcyB2E4JEtKXie/3CVVej+8QKvDorR6kR683Nck6 LIFCTM0aYnRfw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 79D81676DB; Fri, 22 Aug 2025 08:22:13 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 9jDyynpFSC6a; Fri, 22 Aug 2025 08:22:13 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1755872532; bh=miwwxHxy19w/Q51v8mj3lTBbSrK4lR0tE6TL0zlDxCg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=U1VPo/YpZh2KGTa9+7D5EtxBcqniy5W7bePEP1Ip/cLTa22eYd5e7zQn/KsKHq2sP qveHIbqT5qfQ41UV3lUYw+VHwuuflvFzOIRPXZyqqBRMYGzvPtwF8nJ7RnpXoi0QQd G+SuQ1NCcCGqnZt5D5GiNCilQ539NuuZcUkg4E0ZIGHI87WY5paqcANY1uvl44/oEa GxvzoQYkpy5CEN8+VSRCBCQHdsxm0oCIYrdbA5A/6CO73U0SNm2MSNcKotK0DDX7HY o8TyZq53T4wIjDwUU5DudrN2Wn4tzI25Cpdu+wneoFig6yaDmkTqbzgdcfen2YPKsz 1dctKQkRVda1Q== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id B8250676C4; Fri, 22 Aug 2025 08:22:12 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 22 Aug 2025 08:21:32 -0600 Message-ID: <20250822142153.3404275-2-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250822142153.3404275-1-sjg@u-boot.org> References: <20250822142153.3404275-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: XHHU4U3PL5Q2MQ3VZMTIMFJBAPZ5S2R2 X-Message-ID-Hash: XHHU4U3PL5Q2MQ3VZMTIMFJBAPZ5S2R2 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 01/16] console: Add the basic pager implementation List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add 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 --- 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 + */ + +#define LOG_CATEGORY LOGC_CONSOLE + +#include +#include +#include +#include + +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 + */ + +#ifndef __PAGER_H +#define __PAGER_H + +#include +#include +#include +#include + +#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