Compare commits
16 Commits
cherry-822
...
cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
618a97ae79 | ||
|
|
75ea9beef6 | ||
|
|
70bbe178a0 | ||
|
|
03cc6418c1 | ||
|
|
340e2ef64b | ||
|
|
d0f4eee6ba | ||
|
|
6d67faf753 | ||
|
|
1c94482340 | ||
|
|
bf1e7f3646 | ||
|
|
cbdeff3610 | ||
|
|
81ed315188 | ||
|
|
18b67cfa7b | ||
|
|
f904c0cd47 | ||
|
|
b736974fda | ||
|
|
932517d73c | ||
|
|
e7e955ac9d |
@@ -332,6 +332,26 @@ 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.
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
101
common/console.c
101
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>
|
||||
@@ -321,14 +322,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 != NULL)
|
||||
dev->putc(dev, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,17 +386,32 @@ 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 (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 +431,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)
|
||||
{
|
||||
@@ -433,7 +458,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 +470,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 +587,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
|
||||
@@ -740,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);
|
||||
@@ -780,8 +805,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 +1126,36 @@ 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;
|
||||
|
||||
/* 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)
|
||||
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 +1240,7 @@ done:
|
||||
}
|
||||
|
||||
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
|
||||
setup_pager();
|
||||
|
||||
print_pre_console_buffer(flushpoint);
|
||||
return 0;
|
||||
@@ -1252,6 +1308,7 @@ int console_init_r(void)
|
||||
}
|
||||
|
||||
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
|
||||
setup_pager();
|
||||
|
||||
print_pre_console_buffer(flushpoint);
|
||||
return 0;
|
||||
|
||||
208
common/pager.c
Normal file
208
common/pager.c
Normal file
@@ -0,0 +1,208 @@
|
||||
// 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->state == PAGERST_TEST_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)
|
||||
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 "\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;
|
||||
case PAGERST_TEST_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;
|
||||
}
|
||||
|
||||
void pager_uninit(struct pager *pag)
|
||||
{
|
||||
abuf_uninit(&pag->buf);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2000
|
||||
* Paolo Scaffardi, AIRVENT SAM s.p.a - RIMINI(ITALY), arsenio@tin.it
|
||||
*/
|
||||
|
||||
U-Boot console handling
|
||||
========================
|
||||
|
||||
HOW THE CONSOLE WORKS?
|
||||
----------------------
|
||||
|
||||
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).
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
setenv stdin serial <- To use the serial input
|
||||
setenv stdout video <- To use the video console
|
||||
|
||||
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?
|
||||
---------------------------------------------
|
||||
|
||||
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).
|
||||
|
||||
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
|
||||
78
doc/usage/console.rst
Normal file
78
doc/usage/console.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
.. 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
|
||||
=======================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
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).
|
||||
|
||||
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
|
||||
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::
|
||||
|
||||
# Use the serial input
|
||||
setenv stdin serial
|
||||
|
||||
# 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 to output text to the console
|
||||
---------------------------------
|
||||
|
||||
You can use the following functions to access the console:
|
||||
|
||||
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()`.
|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
|
||||
@@ -6,6 +6,7 @@ Use U-Boot
|
||||
|
||||
spl_boot
|
||||
blkmap
|
||||
console
|
||||
dfu
|
||||
environment
|
||||
fdt_overlays
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
178
include/pager.h
Normal file
178
include/pager.h
Normal file
@@ -0,0 +1,178 @@
|
||||
/* 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
|
||||
* @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,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* 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 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_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 void pager_reset(struct pager *pag)
|
||||
{
|
||||
}
|
||||
|
||||
#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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
576
test/common/pager.c
Normal file
576
test/common/pager.c
Normal file
@@ -0,0 +1,576 @@
|
||||
// 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;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
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, 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("\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 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("\n: Press SPACE to continue", 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("\r \r", 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("\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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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("\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);
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user