Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39290ca4cd | ||
|
|
cfad2cabd3 | ||
|
|
0ae6a74826 | ||
|
|
c00ce7e4fd | ||
|
|
4dbff9414e | ||
|
|
5a316de7eb | ||
|
|
3df7ef8813 | ||
|
|
89f7699c81 | ||
|
|
13a69d6460 | ||
|
|
7d82e5c79b | ||
|
|
88549138f6 | ||
|
|
831e273881 | ||
|
|
107c045dde | ||
|
|
e1375a5783 | ||
|
|
d84b68603c | ||
|
|
390bf2c3dd | ||
|
|
f75ea56c11 | ||
|
|
06ff6888a1 | ||
|
|
282f24e87d | ||
|
|
41726522d4 | ||
|
|
363aa5cc8d | ||
|
|
56483c8367 | ||
|
|
a845243258 |
@@ -12,6 +12,7 @@
|
||||
#include <init.h>
|
||||
#include <log.h>
|
||||
#include <os.h>
|
||||
#include <pager.h>
|
||||
#include <sandbox_host.h>
|
||||
#include <sort.h>
|
||||
#include <spl.h>
|
||||
@@ -136,6 +137,10 @@ int sandbox_main_loop_init(void)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
/* Apply pager bypass if requested */
|
||||
if (state->pager_bypass)
|
||||
pager_set_test_bypass(gd_pager(), true);
|
||||
|
||||
/* Execute command if required */
|
||||
if (state->cmd || state->run_distro_boot) {
|
||||
int retval = 0;
|
||||
@@ -433,6 +438,15 @@ static int sandbox_cmdline_cb_autoboot_keyed(struct sandbox_state *state,
|
||||
}
|
||||
SANDBOX_CMDLINE_OPT(autoboot_keyed, 0, "Allow keyed autoboot");
|
||||
|
||||
static int sandbox_cmdline_cb_no_term_present(struct sandbox_state *state,
|
||||
const char *arg)
|
||||
{
|
||||
state->no_term_present = true;
|
||||
return 0;
|
||||
}
|
||||
SANDBOX_CMDLINE_OPT_SHORT(no_term_present, 'A', 0,
|
||||
"Assume no terminal present (for pager testing)");
|
||||
|
||||
static int sandbox_cmdline_cb_upl(struct sandbox_state *state, const char *arg)
|
||||
{
|
||||
state->upl = true;
|
||||
@@ -480,6 +494,16 @@ static int sandbox_cmdline_cb_soft_fail(struct sandbox_state *state,
|
||||
SANDBOX_CMDLINE_OPT_SHORT(soft_fail, 'f', 0,
|
||||
"continue test execution even after it fails");
|
||||
|
||||
static int sandbox_cmdline_cb_pager_bypass(struct sandbox_state *state,
|
||||
const char *arg)
|
||||
{
|
||||
state->pager_bypass = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
SANDBOX_CMDLINE_OPT_SHORT(pager_bypass, 'P', 0,
|
||||
"Enable pager bypass mode");
|
||||
|
||||
static int sandbox_cmdline_cb_bind(struct sandbox_state *state, const char *arg)
|
||||
{
|
||||
if (state->num_binds >= SB_MAX_BINDS) {
|
||||
|
||||
@@ -175,6 +175,8 @@ struct sandbox_state {
|
||||
bool native; /* Adjust to reflect host arch */
|
||||
bool no_flattree_tests; /* Don't run second set of DM tests */
|
||||
bool soft_fail; /* Continue on failure */
|
||||
bool pager_bypass; /* Enable pager-bypass mode */
|
||||
bool no_term_present; /* Assume no terminal present */
|
||||
|
||||
/* Pointer to information for each SPI bus/cs */
|
||||
struct sandbox_spi_info spi[CONFIG_SANDBOX_SPI_MAX_BUS]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <lmb.h>
|
||||
#include <mapmem.h>
|
||||
#include <net.h>
|
||||
#include <pager.h>
|
||||
#include <serial.h>
|
||||
#include <video.h>
|
||||
#include <vsprintf.h>
|
||||
@@ -132,6 +133,8 @@ static int bdinfo_print_all(struct bd_info *bd)
|
||||
lprint_num_l("fdt_blob", (ulong)map_to_sysmem(gd->fdt_blob));
|
||||
if (IS_ENABLED(CONFIG_VIDEO))
|
||||
show_video_info();
|
||||
if (IS_ENABLED(CONFIG_CONSOLE_PAGER))
|
||||
printf("pager = %d\n", gd_pager_page_len());
|
||||
#if CONFIG_IS_ENABLED(MULTI_DTB_FIT)
|
||||
lprint_num_l("multi_dtb_fit", (ulong)gd->multi_dtb_fit);
|
||||
#endif
|
||||
|
||||
@@ -332,6 +332,28 @@ 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
|
||||
imply CMD_CONSOLE_EXTRA
|
||||
imply SERIAL_TERM_PRESENT
|
||||
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.
|
||||
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
170
common/console.c
170
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>
|
||||
@@ -256,14 +257,22 @@ static int console_tstc(int file)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void console_putc(int file, const char c)
|
||||
{
|
||||
int i;
|
||||
struct stdio_dev *dev;
|
||||
static void console_puts_pager(int file, const char *s);
|
||||
|
||||
for_each_console_dev(i, file, dev) {
|
||||
if (dev->putc != NULL)
|
||||
dev->putc(dev, c);
|
||||
static void console_putc_pager(int file, const char c)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) {
|
||||
char str[2] = {c, '\0'};
|
||||
|
||||
console_puts_pager(file, str);
|
||||
} else {
|
||||
int i;
|
||||
struct stdio_dev *dev;
|
||||
|
||||
for_each_console_dev(i, file, dev) {
|
||||
if (dev->putc)
|
||||
dev->putc(dev, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,14 +321,39 @@ 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(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 (IS_ENABLED(CONFIG_CONSOLE_PAGER) && 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +376,39 @@ static inline void console_doenv(int file, struct stdio_dev *dev)
|
||||
iomux_doenv(file, dev->name);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
|
||||
/**
|
||||
* sdev_file_has_uclass() - Find a file has a device of a particular uclass
|
||||
*
|
||||
* Given a file, this returns the first device of the given uclass that is found
|
||||
* within that file.
|
||||
*
|
||||
* This only works with driver model
|
||||
*
|
||||
* @file: File to check (e.g. stdout)
|
||||
* @id: uclass ID to look for
|
||||
* Return: device, if found, NULL if not
|
||||
*/
|
||||
static struct udevice *sdev_file_has_uclass(int file, enum uclass_id id)
|
||||
{
|
||||
struct stdio_dev *sdev;
|
||||
int i;
|
||||
|
||||
for_each_console_dev(i, file, sdev) {
|
||||
if (sdev->flags & DEV_FLAGS_DM) {
|
||||
struct udevice *dev = sdev->priv;
|
||||
|
||||
if (device_get_uclass_id(dev) == id)
|
||||
return dev;
|
||||
} else if (id == UCLASS_SERIAL && console_dev_is_serial(sdev)) {
|
||||
return gd->cur_serial_dev;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#else /* !CONSOLE_MUX */
|
||||
|
||||
static void console_devices_set(int file, struct stdio_dev *dev)
|
||||
{
|
||||
@@ -368,7 +434,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);
|
||||
}
|
||||
@@ -380,7 +446,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);
|
||||
}
|
||||
@@ -399,8 +465,52 @@ static inline void console_doenv(int file, struct stdio_dev *dev)
|
||||
console_setfile(file, dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline struct udevice *sdev_file_has_uclass(int file, enum uclass_id id)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* CONIFIG_IS_ENABLED(CONSOLE_MUX) */
|
||||
|
||||
int calc_check_console_lines(void)
|
||||
{
|
||||
int lines, dev_lines = -1;
|
||||
struct udevice *dev;
|
||||
|
||||
lines = env_get_hex("pager", -1);
|
||||
if (lines != -1)
|
||||
return lines;
|
||||
lines = IF_ENABLED_INT(CONFIG_CONSOLE_PAGER,
|
||||
CONFIG_CONSOLE_PAGER_LINES);
|
||||
|
||||
/* get number of lines from the video console, if available */
|
||||
if (IS_ENABLED(CONFIG_VIDEO) && video_is_visible()) {
|
||||
dev = sdev_file_has_uclass(stdout, UCLASS_VIDEO_CONSOLE);
|
||||
|
||||
if (dev) {
|
||||
struct vidconsole_priv *priv;
|
||||
|
||||
priv = dev_get_uclass_priv(dev);
|
||||
dev_lines = priv->rows;
|
||||
}
|
||||
}
|
||||
/* get number of lines from the serial console, if available */
|
||||
if (IS_ENABLED(CONFIG_DM_SERIAL) &&
|
||||
sdev_file_has_uclass(stdout, UCLASS_SERIAL)) {
|
||||
int cols;
|
||||
|
||||
if (!serial_is_tty())
|
||||
dev_lines = 0;
|
||||
else if (dev_lines == -1)
|
||||
serial_query_size(&dev_lines, &cols);
|
||||
}
|
||||
if (dev_lines != -1)
|
||||
lines = dev_lines;
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
static void __maybe_unused console_setfile_and_devices(int file, struct stdio_dev *dev)
|
||||
{
|
||||
console_setfile(file, dev);
|
||||
@@ -497,13 +607,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
|
||||
@@ -1075,6 +1185,15 @@ static int on_console(const char *name, const char *value, enum env_op op,
|
||||
break;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && console == stdout) {
|
||||
int lines = calc_check_console_lines();
|
||||
|
||||
/* Set bypass mode if not connected to a terminal */
|
||||
pager_set_bypass(gd_pager(), lines != 0);
|
||||
if (lines)
|
||||
pager_set_page_len(gd_pager(), lines);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
U_BOOT_ENV_CALLBACK(console, on_console);
|
||||
@@ -1101,6 +1220,19 @@ static int on_silent(const char *name, const char *value, enum env_op op,
|
||||
U_BOOT_ENV_CALLBACK(silent, on_silent);
|
||||
#endif
|
||||
|
||||
static void setup_pager(void)
|
||||
{
|
||||
/* Init pager now that console is ready */
|
||||
if (IS_ENABLED(CONFIG_CONSOLE_PAGER)) {
|
||||
int ret;
|
||||
|
||||
ret = pager_init(gd_pagerp(), calc_check_console_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 +1317,7 @@ done:
|
||||
}
|
||||
|
||||
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
|
||||
setup_pager();
|
||||
|
||||
print_pre_console_buffer(flushpoint);
|
||||
return 0;
|
||||
@@ -1252,6 +1385,7 @@ int console_init_r(void)
|
||||
}
|
||||
|
||||
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
|
||||
setup_pager();
|
||||
|
||||
print_pre_console_buffer(flushpoint);
|
||||
return 0;
|
||||
|
||||
219
common/pager.c
Normal file
219
common/pager.c
Normal file
@@ -0,0 +1,219 @@
|
||||
// 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 <env.h>
|
||||
#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, bool use_pager, const char *s)
|
||||
{
|
||||
struct membuf old;
|
||||
int ret, len;
|
||||
|
||||
if (!pag || !use_pager || pag->test_bypass || pag->state == PAGERST_BYPASS)
|
||||
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, true, 0);
|
||||
}
|
||||
|
||||
const char *pager_next(struct pager *pag, bool use_pager, int key)
|
||||
{
|
||||
char *str, *p, *end;
|
||||
int ret;
|
||||
|
||||
if (!use_pager || (pag && pag->test_bypass))
|
||||
return NULL;
|
||||
|
||||
/* 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 PAGER_PROMPT;
|
||||
case PAGERST_WAIT_USER:
|
||||
if (key != ' ')
|
||||
return PAGER_WAITING;
|
||||
pag->state = PAGERST_CLEAR_PROMPT;
|
||||
return PAGER_BLANK;
|
||||
case PAGERST_CLEAR_PROMPT:
|
||||
pag->state = PAGERST_OK;
|
||||
break;
|
||||
case PAGERST_BYPASS:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool pager_set_bypass(struct pager *pag, bool bypass)
|
||||
{
|
||||
bool was_bypassed = false;
|
||||
|
||||
if (!pag)
|
||||
return false;
|
||||
|
||||
was_bypassed = pag->state == PAGERST_BYPASS;
|
||||
if (bypass)
|
||||
pag->state = PAGERST_BYPASS;
|
||||
else
|
||||
pag->state = PAGERST_OK;
|
||||
|
||||
return was_bypassed;
|
||||
}
|
||||
|
||||
bool pager_set_test_bypass(struct pager *pag, bool bypass)
|
||||
{
|
||||
bool was_bypassed = false;
|
||||
|
||||
if (!pag)
|
||||
return false;
|
||||
|
||||
was_bypassed = pag->test_bypass;
|
||||
pag->test_bypass = 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -20,6 +20,7 @@ U-Boot API documentation
|
||||
lmb
|
||||
logging
|
||||
nvmem
|
||||
pager
|
||||
part
|
||||
pinctrl
|
||||
rng
|
||||
|
||||
10
doc/api/pager.rst
Normal file
10
doc/api/pager.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
.. Copyright Simon Glass <sjg@chromium.org>
|
||||
|
||||
Console Pager
|
||||
=============
|
||||
|
||||
See :doc:`/develop/console`.
|
||||
|
||||
.. kernel-doc:: include/pager.h
|
||||
:internal:
|
||||
41
doc/develop/console.rst
Normal file
41
doc/develop/console.rst
Normal file
@@ -0,0 +1,41 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
.. sectionauthor:: Simon Glass <sjg@chromium.org>
|
||||
|
||||
=======
|
||||
Console
|
||||
=======
|
||||
|
||||
|
||||
Paging
|
||||
------
|
||||
|
||||
See the user documentation at :doc:`/usage/console`. For the API, see
|
||||
:doc:`/api/pager`.
|
||||
|
||||
The pager is implemented in `common/pager.c` and integrated into the console
|
||||
system via `console_puts()` in `common/console.c`. When output is sent to
|
||||
stdout:
|
||||
|
||||
1. Text is passed to `pager_post()` which buffers it
|
||||
2. `pager_next()` returns portions of text up to the page limit
|
||||
3. When the page limit is reached, a prompt is displayed
|
||||
4. The system waits for user input (SPACE key) via `getchar()`
|
||||
5. After SPACE is pressed, the prompt is cleared and output continues
|
||||
|
||||
The pager maintains state through the `struct pager` which tracks:
|
||||
|
||||
- Current line count within the page
|
||||
- Page length setting
|
||||
- Text buffer and overflow handling
|
||||
- Current pager state (normal, waiting for user, clearing prompt)
|
||||
|
||||
Bypass Mode
|
||||
~~~~~~~~~~~
|
||||
|
||||
The pager can be put into bypass mode using `pager_set_bypass()`, which is
|
||||
useful for automated testing. In bypass mode, all text flows through without
|
||||
any paging interruptions.
|
||||
|
||||
The pager automatically enters bypass mode when output is not connected to a
|
||||
terminal, preventing paging prompts from appearing in non-interactive
|
||||
environments like scripts or automated builds.
|
||||
@@ -35,6 +35,7 @@ Implementation
|
||||
ci_testing
|
||||
commands
|
||||
config_binding
|
||||
console
|
||||
cyclic
|
||||
devicetree/index
|
||||
distro
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
.. sectionauthor:: Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it
|
||||
.. (C) Copyright 2000
|
||||
.. sectionauthor:: Simon Glass <sjg@chromium.org>
|
||||
|
||||
=======================
|
||||
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 +18,90 @@ 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)
|
||||
stdout
|
||||
- putc() - write a char to stdout
|
||||
- puts() - write a string to stdout
|
||||
- printf() - format and write a string to stdout
|
||||
|
||||
* STDIN:
|
||||
tstc (to test for the presence of a char in stdin)
|
||||
getc (to get a char from stdin)
|
||||
stdin
|
||||
- tstc() - test for the presence of a char in stdin
|
||||
- getchar() - 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)
|
||||
stderr
|
||||
- eputc() - write a char to stderr
|
||||
- eputs() - write a string to stderr
|
||||
- eprintf() - format and write 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)
|
||||
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 all FILE-related functions CANNOT be used before
|
||||
U-Boot relocation (done in 'board_init_r' in `arch/*/lib/board.c`).
|
||||
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 `CONFIG_CONSOLE_MUX` is also enabled.
|
||||
|
||||
When activated, the pager pauses at the end of each 'page' (screenful) of
|
||||
output, shows a prompt ": Press SPACE to continue" and lets the user read the
|
||||
output. To continue to the next page, press the SPACE key.
|
||||
|
||||
Page Size Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The number of lines per page is determined in the following order of priority:
|
||||
|
||||
1. **Environment variable**: The `pager` environment variable (hex value)
|
||||
takes highest priority. Set to 0 to disable paging.
|
||||
|
||||
2. **Video console detection**: If no environment variable is set and a video
|
||||
console is active, the pager uses the number of rows from the video console.
|
||||
|
||||
3. **Serial TTY detection**: For serial consoles, the pager checks if the
|
||||
output is connected to a terminal (TTY). If not connected to a TTY, paging
|
||||
is disabled. This check works by sending a few special characters to the
|
||||
terminal and (hopefully) receiving a reply. If you are logging the output of
|
||||
U-Boot, you may see these characters in the log. Disable
|
||||
`CONFIG_SERIAL_TERM_PRESENT` is this is unwanted.
|
||||
|
||||
4. **Configuration default**: If none of the above apply, falls back to
|
||||
`CONFIG_CONSOLE_PAGER_LINES`.
|
||||
|
||||
Examples::
|
||||
|
||||
# Set page size to 30 lines (hex value 1e)
|
||||
setenv pager 1e
|
||||
|
||||
# Set page size to 24 lines (hex value 18)
|
||||
setenv pager 18
|
||||
|
||||
# Disable paging
|
||||
setenv pager 0
|
||||
|
||||
For developer documentation, please see :doc:`/develop/console`.
|
||||
|
||||
@@ -335,6 +335,10 @@ netretry
|
||||
Useful on scripts which control the retry operation
|
||||
themselves.
|
||||
|
||||
pager
|
||||
Decimal number of visible lines on the display, or serial console.
|
||||
:doc:`/usage/console`.
|
||||
|
||||
rng_seed_size
|
||||
Size of random value added to device-tree node /chosen/rng-seed.
|
||||
This variable is given as a decimal number.
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include <asm/global_data.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/device-internal.h>
|
||||
#ifdef CONFIG_SANDBOX
|
||||
#include <asm/state.h>
|
||||
#endif
|
||||
#include <dm/of_access.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/delay.h>
|
||||
@@ -647,6 +650,14 @@ int serial_query_size(int *rowsp, int *colsp)
|
||||
if (!CONFIG_IS_ENABLED(SERIAL_TERM_PRESENT))
|
||||
return -ENOENT;
|
||||
|
||||
#ifdef CONFIG_SANDBOX
|
||||
if (IS_ENABLED(CONFIG_SANDBOX)) {
|
||||
struct sandbox_state *state = state_get_current();
|
||||
if (state->no_term_present)
|
||||
return -ENOENT;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Empty input buffer */
|
||||
while (tstc())
|
||||
getchar();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <event_internal.h>
|
||||
#include <fdtdec.h>
|
||||
#include <membuf.h>
|
||||
#include <pager.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <asm-offsets.h>
|
||||
@@ -477,6 +478,14 @@ struct global_data {
|
||||
*/
|
||||
struct upl *upl;
|
||||
#endif
|
||||
#if CONFIG_IS_ENABLED(CONSOLE_PAGER)
|
||||
/**
|
||||
* @pager: Pointer to the pager settings, or NULL if none
|
||||
*
|
||||
* This is set up in console_init_r()
|
||||
*/
|
||||
struct pager *pager;
|
||||
#endif
|
||||
};
|
||||
#ifndef DO_DEPS_ONLY
|
||||
static_assert(sizeof(struct global_data) == GD_SIZE);
|
||||
@@ -618,6 +627,16 @@ 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
|
||||
#define gd_pager_page_len() gd->pager->page_len
|
||||
#else
|
||||
#define gd_pager() NULL
|
||||
#define gd_pagerp() NULL
|
||||
#define gd_pager_page_len() 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* enum gd_flags - global data flags
|
||||
*
|
||||
|
||||
@@ -202,6 +202,16 @@ int console_clear(void);
|
||||
*/
|
||||
int console_remove_by_name(const char *name);
|
||||
|
||||
/**
|
||||
* calc_check_console_lines() - Calculate console page length
|
||||
*
|
||||
* This calculates the appropriate number of lines to use for console paging,
|
||||
* considering environment variables, config defaults, and device capabilities.
|
||||
*
|
||||
* Return: number of lines for paging, or 0 to disable paging
|
||||
*/
|
||||
int calc_check_console_lines(void);
|
||||
|
||||
/*
|
||||
* CONSOLE multiplexing.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
212
include/pager.h
Normal file
212
include/pager.h
Normal file
@@ -0,0 +1,212 @@
|
||||
/* 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 waiting for user input */
|
||||
#define PAGER_WAITING ((const char *)1)
|
||||
|
||||
/* Prompt shown to user when pager reaches page limit */
|
||||
#define PAGER_PROMPT "\n: Press SPACE to continue"
|
||||
|
||||
/* String used to blank/clear the pager prompt */
|
||||
#define PAGER_BLANK "\r \r"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @PAGERST_BYPASS: Pager is being bypassed
|
||||
*/
|
||||
enum pager_state {
|
||||
PAGERST_OK,
|
||||
PAGERST_AT_LIMIT,
|
||||
PAGERST_WAIT_USER,
|
||||
PAGERST_CLEAR_PROMPT,
|
||||
PAGERST_BYPASS,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @state: current state of the pager state-machine
|
||||
* @test_bypass: true if pager should behave as if in test mode (bypass all)
|
||||
*/
|
||||
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;
|
||||
bool test_bypass;
|
||||
};
|
||||
|
||||
#if CONFIG_IS_ENABLED(CONSOLE_PAGER)
|
||||
|
||||
/**
|
||||
* pager_post() - Add text to the input buffer for later handling
|
||||
*
|
||||
* 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
|
||||
* 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
|
||||
* @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, bool use_pager, 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
|
||||
* @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, bool use_pager, int ch);
|
||||
|
||||
/**
|
||||
* pager_set_bypass() - put the pager into bypass mode
|
||||
*
|
||||
* This is used when output is not connected to a terminal. 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 for normal mode
|
||||
* Return: old value of the bypass flag
|
||||
*/
|
||||
bool pager_set_bypass(struct pager *pag, bool bypass);
|
||||
|
||||
/**
|
||||
* pager_set_test_bypass() - put the pager into test bypass mode
|
||||
*
|
||||
* This is used for tests. Test bypass mode stops the pager from doing
|
||||
* anything to interrupt output, regardless of the current pager state.
|
||||
*
|
||||
* @pag: Pager to use, may be NULL in which case this function does nothing
|
||||
* @bypass: true to put the pager in test bypass mode, false for normal
|
||||
* Return: old value of the test bypass flag
|
||||
*/
|
||||
bool pager_set_test_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
|
||||
*
|
||||
* 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, bool use_pager,
|
||||
const char *s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static inline bool pager_set_test_bypass(struct pager *pag, bool bypass)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void pager_reset(struct pager *pag)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* pager_set_page_len() - Set the page length of a pager
|
||||
*
|
||||
* @pag: Pager to use
|
||||
* @page_len: Page length to set
|
||||
*/
|
||||
void pager_set_page_len(struct pager *pag, int page_len);
|
||||
|
||||
/**
|
||||
* pager_init() - Set up a new pager
|
||||
*
|
||||
* @pagp: Returns allocaed pager, on success
|
||||
* @page_len: 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
|
||||
@@ -406,8 +406,8 @@ int serial_tstc(void);
|
||||
* When using a serial console or the net console we can only devise the
|
||||
* terminal size by querying the terminal using ECMA-48 control sequences.
|
||||
*
|
||||
* @rowsp: returns number of rows
|
||||
* @colsp: returns number of columns
|
||||
* @rowsp: returns number of rows on success
|
||||
* @colsp: returns number of columns on success
|
||||
* Returns: 0 on success, -NOENT if no terminal is present, -ETIMEDOUT if we
|
||||
* checked for a terminal but didn't get a response in time, -EPROTO if the
|
||||
* terminal did not respond as expected
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -184,6 +184,9 @@ static int bdinfo_test_all(struct unit_test_state *uts)
|
||||
if (IS_ENABLED(CONFIG_VIDEO))
|
||||
ut_assertok(test_video_info(uts));
|
||||
|
||||
if (IS_ENABLED(CONFIG_CONSOLE_PAGER))
|
||||
ut_assert_nextline("pager = %d", gd_pager_page_len());
|
||||
|
||||
/* The gd->multi_dtb_fit may not be available, hence, #if below. */
|
||||
#if CONFIG_IS_ENABLED(MULTI_DTB_FIT)
|
||||
ut_assertok(test_num_l(uts, "multi_dtb_fit", (ulong)gd->multi_dtb_fit));
|
||||
|
||||
@@ -7,7 +7,9 @@ obj-$(CONFIG_$(PHASE_)CMDLINE) += bloblist.o
|
||||
endif
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_CONSOLE_PAGER) += console.o
|
||||
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
|
||||
|
||||
183
test/common/console.c
Normal file
183
test/common/console.c
Normal file
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*
|
||||
* Test for console functionality
|
||||
*/
|
||||
|
||||
#include <console.h>
|
||||
#include <env.h>
|
||||
#include <malloc.h>
|
||||
#include <serial.h>
|
||||
#include <video.h>
|
||||
#include <test/common.h>
|
||||
#include <test/test.h>
|
||||
#include <test/ut.h>
|
||||
#include <asm/global_data.h>
|
||||
#include <asm/state.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
/* Test calc_check_console_lines() with environment variable override */
|
||||
static int console_test_calc_lines_env_override(struct unit_test_state *uts)
|
||||
{
|
||||
int original_lines, lines;
|
||||
char *orig_env;
|
||||
|
||||
/* Save original environment value */
|
||||
orig_env = env_get("pager");
|
||||
original_lines = calc_check_console_lines();
|
||||
|
||||
/* Test with hex environment variable */
|
||||
ut_assertok(env_set("pager", "1a")); /* 26 in hex */
|
||||
lines = calc_check_console_lines();
|
||||
ut_asserteq(26, lines);
|
||||
|
||||
/* Test with decimal-looking hex value */
|
||||
ut_assertok(env_set("pager", "20")); /* 32 in hex */
|
||||
lines = calc_check_console_lines();
|
||||
ut_asserteq(32, lines);
|
||||
|
||||
/* Test with zero (should disable pager) */
|
||||
ut_assertok(env_set("pager", "0"));
|
||||
lines = calc_check_console_lines();
|
||||
ut_asserteq(0, lines);
|
||||
|
||||
/* Restore original environment */
|
||||
ut_assertok(env_set("pager", orig_env));
|
||||
|
||||
/* Verify restoration */
|
||||
lines = calc_check_console_lines();
|
||||
ut_asserteq(original_lines, lines);
|
||||
|
||||
return 0;
|
||||
}
|
||||
COMMON_TEST(console_test_calc_lines_env_override, 0);
|
||||
|
||||
/* Test calc_check_console_lines() with invalid environment values */
|
||||
static int console_test_calc_lines_env_invalid(struct unit_test_state *uts)
|
||||
{
|
||||
int original_lines, lines;
|
||||
char *orig_env;
|
||||
|
||||
/* Save original environment value */
|
||||
orig_env = env_get("pager");
|
||||
original_lines = calc_check_console_lines();
|
||||
|
||||
/* Test with invalid hex value - should fall back to default */
|
||||
ut_assertok(env_set("pager", "xyz"));
|
||||
lines = calc_check_console_lines();
|
||||
/* Should return default since 'pager' reads as 0 */
|
||||
ut_asserteq(CONFIG_CONSOLE_PAGER_LINES, lines);
|
||||
|
||||
/* Test with empty string */
|
||||
ut_assertok(env_set("pager", ""));
|
||||
lines = calc_check_console_lines();
|
||||
ut_asserteq(CONFIG_CONSOLE_PAGER_LINES, lines);
|
||||
|
||||
/* Restore original environment */
|
||||
ut_assertok(env_set("pager", orig_env));
|
||||
|
||||
return 0;
|
||||
}
|
||||
COMMON_TEST(console_test_calc_lines_env_invalid, 0);
|
||||
|
||||
/* Test calc_check_console_lines() default behavior without environment */
|
||||
static int console_test_calc_lines_default(struct unit_test_state *uts)
|
||||
{
|
||||
int lines;
|
||||
char *orig_env;
|
||||
|
||||
/* Save and clear environment variable */
|
||||
orig_env = env_get("pager");
|
||||
ut_assertok(env_set("pager", NULL));
|
||||
|
||||
/* Get default behavior */
|
||||
lines = calc_check_console_lines();
|
||||
|
||||
/*
|
||||
* The function considers device detection (video/serial) which may
|
||||
* override the CONFIG default. In sandbox, serial_is_tty() may return
|
||||
* false during tests, causing the function to return 0. Just verify it
|
||||
* returns a reasonable value.
|
||||
*/
|
||||
ut_assert(lines >= 0);
|
||||
|
||||
/* If CONFIG_CONSOLE_PAGER is not enabled, should definitely be 0 */
|
||||
if (!IS_ENABLED(CONFIG_CONSOLE_PAGER)) {
|
||||
ut_asserteq(0, lines);
|
||||
}
|
||||
|
||||
/* Restore original environment */
|
||||
ut_assertok(env_set("pager", orig_env));
|
||||
|
||||
return 0;
|
||||
}
|
||||
COMMON_TEST(console_test_calc_lines_default, 0);
|
||||
|
||||
/* Test calc_check_console_lines() precedence: env overrides everything */
|
||||
static int console_test_calc_lines_precedence(struct unit_test_state *uts)
|
||||
{
|
||||
int lines;
|
||||
char *orig_env;
|
||||
|
||||
/* Save original environment value */
|
||||
orig_env = env_get("pager");
|
||||
|
||||
/* Set environment to a specific value */
|
||||
ut_assertok(env_set("pager", "2a")); /* 42 in hex */
|
||||
lines = calc_check_console_lines();
|
||||
|
||||
/*
|
||||
* Environment should always take precedence regardless of video/serial
|
||||
* state
|
||||
*/
|
||||
ut_asserteq(42, lines);
|
||||
|
||||
/* Test with zero environment value */
|
||||
ut_assertok(env_set("pager", "0"));
|
||||
lines = calc_check_console_lines();
|
||||
ut_asserteq(0, lines);
|
||||
|
||||
/* Restore original environment */
|
||||
ut_assertok(env_set("pager", orig_env));
|
||||
|
||||
return 0;
|
||||
}
|
||||
COMMON_TEST(console_test_calc_lines_precedence, 0);
|
||||
|
||||
/* Test calc_check_console_lines() with serial TTY detection */
|
||||
static int console_test_calc_lines_serial_tty(struct unit_test_state *uts)
|
||||
{
|
||||
int lines;
|
||||
char *orig_env;
|
||||
bool orig_tty;
|
||||
struct sandbox_state *state;
|
||||
|
||||
/* Save original state */
|
||||
orig_env = env_get("pager");
|
||||
state = state_get_current();
|
||||
orig_tty = state->serial_is_tty;
|
||||
|
||||
/* Clear environment to test device detection */
|
||||
ut_assertok(env_set("pager", NULL));
|
||||
|
||||
/* Test with serial TTY enabled */
|
||||
state->serial_is_tty = true;
|
||||
lines = calc_check_console_lines();
|
||||
/* Should attempt to query serial size or use default */
|
||||
ut_assert(lines >= 0);
|
||||
|
||||
/* Test with serial TTY disabled (not a terminal) */
|
||||
state->serial_is_tty = false;
|
||||
lines = calc_check_console_lines();
|
||||
/* Should return 0 when not connected to TTY */
|
||||
ut_asserteq(0, lines);
|
||||
|
||||
/* Restore original state */
|
||||
state->serial_is_tty = orig_tty;
|
||||
ut_assertok(env_set("pager", orig_env));
|
||||
|
||||
return 0;
|
||||
}
|
||||
COMMON_TEST(console_test_calc_lines_serial_tty, 0);
|
||||
580
test/common/pager.c
Normal file
580
test/common/pager.c
Normal file
@@ -0,0 +1,580 @@
|
||||
// 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>
|
||||
#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)
|
||||
{
|
||||
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, true, text);
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(text, result);
|
||||
|
||||
/* Should be no more text */
|
||||
result = pager_next(pag, true, 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;
|
||||
ulong start;
|
||||
|
||||
start = ut_check_free();
|
||||
|
||||
ut_assertok(pager_init(&pag, 20, 1024));
|
||||
|
||||
/* Post multiple pieces of text */
|
||||
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, true, 0);
|
||||
ut_assertnull(result);
|
||||
|
||||
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, true, 0);
|
||||
ut_assertnull(result);
|
||||
|
||||
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, true, 0);
|
||||
ut_assertnull(result);
|
||||
|
||||
pager_uninit(pag);
|
||||
|
||||
/* Check for memory leaks */
|
||||
ut_assertok(ut_check_delta(start));
|
||||
|
||||
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, true, "this is 16 chars");
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str("this is 16 chars", result);
|
||||
ut_assertnull(pager_next(pag, true, 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, true, "test1");
|
||||
ut_assertnonnull(result);
|
||||
|
||||
/* overflow handling should return the text */
|
||||
ut_asserteq_str("test1", result);
|
||||
ut_assertnull(pager_next(pag, true, 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, true, "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, true, "");
|
||||
ut_assertnull(result);
|
||||
|
||||
/* Should be no more text */
|
||||
result = pager_next(pag, true, 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, true, 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;
|
||||
|
||||
ut_assertok(pager_init(&pag, 20, 1024));
|
||||
|
||||
/* Create a very long line without newlines */
|
||||
memset(long_line, 'X', sizeof(long_line) - 1);
|
||||
long_line[sizeof(long_line) - 1] = '\0';
|
||||
|
||||
/* Post the long line */
|
||||
result = pager_post(pag, true, 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, true, 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, true, 0);
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_PROMPT, result);
|
||||
|
||||
/* Press space to continue */
|
||||
result = pager_next(pag, true, ' ');
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_BLANK, result);
|
||||
|
||||
/* Get remaining lines */
|
||||
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, true, 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, 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, true, 0);
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_PROMPT, result);
|
||||
|
||||
/* Next call without space key should return PAGER_WAITING */
|
||||
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, true, 'x'); /* Wrong key */
|
||||
ut_asserteq_ptr(PAGER_WAITING, result);
|
||||
|
||||
/* Pressing space should clear the prompt */
|
||||
result = pager_next(pag, true, ' ');
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_BLANK, result);
|
||||
|
||||
/* Now should return NULL (no more content) */
|
||||
result = pager_next(pag, true, 0);
|
||||
ut_assertnull(result);
|
||||
|
||||
pager_uninit(pag);
|
||||
|
||||
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(PAGER_PROMPT, result);
|
||||
|
||||
/* Press space to continue */
|
||||
result = pager_next(pag, true, ' ');
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_BLANK, 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(PAGER_PROMPT, result);
|
||||
|
||||
/* Press space to continue */
|
||||
result = pager_next(pag, true, ' ');
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_BLANK, 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);
|
||||
|
||||
/* 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_test_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_test_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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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(PAGER_PROMPT, result);
|
||||
|
||||
/* Press space to continue */
|
||||
result = pager_next(pag, true, ' ');
|
||||
ut_assertnonnull(result);
|
||||
ut_asserteq_str(PAGER_BLANK, 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);
|
||||
@@ -52,6 +52,13 @@ class ConsoleSandbox(ConsoleBase):
|
||||
if self.use_dtb:
|
||||
cmd += ['-d', self.config.dtb]
|
||||
cmd += self.sandbox_flags
|
||||
|
||||
# Always disable the pager
|
||||
cmd.append('-P')
|
||||
|
||||
# Always disable detected the terminal size
|
||||
cmd.append('-A')
|
||||
|
||||
return Spawn(cmd, cwd=self.config.source_dir, decode_signal=True)
|
||||
|
||||
def restart_uboot_with_flags(self, flags, use_dtb=True):
|
||||
|
||||
@@ -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_test_bypass(gd_pager(), true);
|
||||
ret = ut_run_tests(uts, prefix, tests, count, select_name,
|
||||
test_insert);
|
||||
pager_set_test_bypass(gd_pager(), false);
|
||||
|
||||
/* Best efforts only...ignore errors */
|
||||
if (has_dm_tests)
|
||||
|
||||
Reference in New Issue
Block a user