lib: add cbprintf capability
This commit adds a C99 stdio value formatter capability where generated text is emitted through a callback. This allows generation of arbitrarily long output without a buffer, functionality that is core to printk, logging, and other system and application needs. The formatter supports most C99 specifications, excluding: * %Lf long double conversion * wide character output Kconfig options allow disabling features like floating-point conversion if they are not necessary. By default most conversions are enabled. The original z_vprintk() implementation is adapted to meet the interface requirements of cbvprintf, and made available as an opt-in feature for space-constrained applications that do not need full formatting support. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
This commit is contained in:
parent
abb3a28c94
commit
33103828dc
9 changed files with 2584 additions and 0 deletions
|
@ -17,6 +17,7 @@ API Reference
|
||||||
drivers/index.rst
|
drivers/index.rst
|
||||||
display/index.rst
|
display/index.rst
|
||||||
file_system/index.rst
|
file_system/index.rst
|
||||||
|
misc/formatted_output.rst
|
||||||
kernel/index.rst
|
kernel/index.rst
|
||||||
logging/index.rst
|
logging/index.rst
|
||||||
misc/index
|
misc/index
|
||||||
|
|
55
doc/reference/misc/formatted_output.rst
Normal file
55
doc/reference/misc/formatted_output.rst
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
.. _formatted_output:
|
||||||
|
|
||||||
|
Formatted Output
|
||||||
|
################
|
||||||
|
|
||||||
|
Applications as well as Zephyr itself requires infrastructure to format
|
||||||
|
values for user consumption. The standard C99 library ``*printf()``
|
||||||
|
functionality fulfills this need for streaming output devices or memory
|
||||||
|
buffers, but in an embedded system devices may not accept streamed data
|
||||||
|
and memory may not be available to store the formatted output.
|
||||||
|
|
||||||
|
Internal Zephyr API traditionally provided this both for
|
||||||
|
:c:func:`printk` and for Zephyr's internal minimal libc, but with
|
||||||
|
separate internal interfaces. Logging, tracing, shell, and other
|
||||||
|
applications made use of either these APIs or standard libc routines
|
||||||
|
based on build options.
|
||||||
|
|
||||||
|
The :c:func:`cbprintf` public APIs convert C99 format strings and
|
||||||
|
arguments, providing output produced one character at a time through a
|
||||||
|
callback mechanism, replacing the original internal functions and
|
||||||
|
providing support for almost all C99 format specifications. Existing
|
||||||
|
use of ``s*printf()`` C libraries in Zephyr can be converted to
|
||||||
|
:c:func:`snprintfcb()` to avoid pulling in libc implementations.
|
||||||
|
|
||||||
|
Several Kconfig options control the set of features that are enabled,
|
||||||
|
allowing some control over features and memory usage:
|
||||||
|
|
||||||
|
* :option:`CONFIG_CBPRINTF_FULL_INTEGRAL`
|
||||||
|
or :option:`CONFIG_CBPRINTF_REDUCED_INTEGRAL`
|
||||||
|
* :option:`CONFIG_CBPRINTF_FP_SUPPORT`
|
||||||
|
* :option:`CONFIG_CBPRINTF_FP_A_SUPPORT`
|
||||||
|
* :option:`CONFIG_CBPRINTF_FP_ALWAYS_A`
|
||||||
|
* :option:`CONFIG_CBPRINTF_N_SPECIFIER`
|
||||||
|
|
||||||
|
:option:`CONFIG_CBPRINTF_LIBC_SUBSTS` can be used to provide functions
|
||||||
|
that behave like standard libc functions but use the selected cbprintf
|
||||||
|
formatter rather than pulling in another formatter from libc.
|
||||||
|
|
||||||
|
In addition :option:`CONFIG_CBPRINTF_NANO` can be used to revert back to
|
||||||
|
the very space-optimized but limited formatter used for :c:func:`printk`
|
||||||
|
before this capability was added.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If :option:`CONFIG_MINIMAL_LIBC` is selected in combination with
|
||||||
|
:option:`CONFIG_CBPRINTF_NANO` formatting with C standard library
|
||||||
|
functions like ``printf`` or ``snprintf`` is limited. Among other
|
||||||
|
things the ``%n`` specifier, most format flags, precision control, and
|
||||||
|
floating point are not supported.
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
*************
|
||||||
|
|
||||||
|
.. doxygengroup:: cbprintf_apis
|
||||||
|
:project: Zephyr
|
177
include/sys/cbprintf.h
Normal file
177
include/sys/cbprintf.h
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_INCLUDE_SYS_CBPRINTF_H_
|
||||||
|
#define ZEPHYR_INCLUDE_SYS_CBPRINTF_H_
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <toolchain.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup cbprintf_apis Formatted Output APIs
|
||||||
|
* @ingroup support_apis
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief Signature for a cbprintf callback function.
|
||||||
|
*
|
||||||
|
* This function expects two parameters:
|
||||||
|
*
|
||||||
|
* * @p c a character to output. The output behavior should be as if
|
||||||
|
* this was cast to an unsigned char.
|
||||||
|
* * @p ctx a pointer to an object that provides context for the
|
||||||
|
* output operation.
|
||||||
|
*
|
||||||
|
* The declaration does not specify the parameter types. This allows a
|
||||||
|
* function like @c fputc to be used without requiring all context pointers to
|
||||||
|
* be to a @c FILE object.
|
||||||
|
*
|
||||||
|
* @return the value of @p c cast to an unsigned char then back to
|
||||||
|
* int, or a negative error code that will be returned from
|
||||||
|
* cbprintf().
|
||||||
|
*/
|
||||||
|
typedef int (*cbprintf_cb)(/* int c, void *ctx */);
|
||||||
|
|
||||||
|
/** @brief *printf-like output through a callback.
|
||||||
|
*
|
||||||
|
* This is essentially printf() except the output is generated
|
||||||
|
* character-by-character using the provided @p out function. This allows
|
||||||
|
* formatting text of unbounded length without incurring the cost of a
|
||||||
|
* temporary buffer.
|
||||||
|
*
|
||||||
|
* All formatting specifiers of C99 are recognized, and most are supported if
|
||||||
|
* the functionality is enabled.
|
||||||
|
*
|
||||||
|
* @note The functionality of this function is significantly reduced
|
||||||
|
* when `CONFIG_CBPRINTF_NANO` is selected.
|
||||||
|
*
|
||||||
|
* @param out the function used to emit each generated character.
|
||||||
|
*
|
||||||
|
* @param ctx context provided when invoking out
|
||||||
|
*
|
||||||
|
* @param format a standard ISO C format string with characters and conversion
|
||||||
|
* specifications.
|
||||||
|
*
|
||||||
|
* @param ... arguments corresponding to the conversion specifications found
|
||||||
|
* within @p format.
|
||||||
|
*
|
||||||
|
* @return the number of characters printed, or a negative error value
|
||||||
|
* returned from invoking @p out.
|
||||||
|
*/
|
||||||
|
__printf_like(3, 4)
|
||||||
|
int cbprintf(cbprintf_cb out, void *ctx, const char *format, ...);
|
||||||
|
|
||||||
|
/** @brief Calculate the number of words required for arguments to a cbprintf
|
||||||
|
* format specification.
|
||||||
|
*
|
||||||
|
* This can be used in cases where the arguments must be copied off the stack
|
||||||
|
* into separate storage for processing the conversion in another context.
|
||||||
|
*
|
||||||
|
* @note The length returned does not count bytes. It counts native words
|
||||||
|
* defined as the size of an `int`.
|
||||||
|
*
|
||||||
|
* @note If `CONFIG_CBPRINTF_NANO` is selected the count will be incorrect if
|
||||||
|
* any passed arguments require more than one `int`.
|
||||||
|
*
|
||||||
|
* @param format a standard ISO C format string with characters and conversion
|
||||||
|
* specifications.
|
||||||
|
*
|
||||||
|
* @return the number of `int` elements required to provide all arguments
|
||||||
|
* required for the conversion.
|
||||||
|
*/
|
||||||
|
size_t cbprintf_arglen(const char *format);
|
||||||
|
|
||||||
|
/** @brief varargs-aware *printf-like output through a callback.
|
||||||
|
*
|
||||||
|
* This is essentially vsprintf() except the output is generated
|
||||||
|
* character-by-character using the provided @p out function. This allows
|
||||||
|
* formatting text of unbounded length without incurring the cost of a
|
||||||
|
* temporary buffer.
|
||||||
|
*
|
||||||
|
* @note This function is available only when `CONFIG_CBPRINTF_LIBC_SUBSTS` is
|
||||||
|
* selected.
|
||||||
|
*
|
||||||
|
* @note The functionality of this function is significantly reduced when
|
||||||
|
* `CONFIG_CBPRINTF_NANO` is selected.
|
||||||
|
*
|
||||||
|
* @param out the function used to emit each generated character.
|
||||||
|
*
|
||||||
|
* @param ctx context provided when invoking out
|
||||||
|
*
|
||||||
|
* @param format a standard ISO C format string with characters and conversion
|
||||||
|
* specifications.
|
||||||
|
*
|
||||||
|
* @param ap a reference to the values to be converted.
|
||||||
|
*
|
||||||
|
* @return the number of characters generated, or a negative error value
|
||||||
|
* returned from invoking @p out.
|
||||||
|
*/
|
||||||
|
int cbvprintf(cbprintf_cb out, void *ctx, const char *format, va_list ap);
|
||||||
|
|
||||||
|
/** @brief snprintf using Zephyrs cbprintf infrastructure.
|
||||||
|
*
|
||||||
|
* @note The functionality of this function is significantly reduced
|
||||||
|
* when `CONFIG_CBPRINTF_NANO` is selected.
|
||||||
|
*
|
||||||
|
* @param str where the formatted content should be written
|
||||||
|
*
|
||||||
|
* @param size maximum number of chaacters for the formatted output,
|
||||||
|
* including the terminating null byte.
|
||||||
|
*
|
||||||
|
* @param format a standard ISO C format string with characters and
|
||||||
|
* conversion specifications.
|
||||||
|
*
|
||||||
|
* @param ... arguments corresponding to the conversion specifications found
|
||||||
|
* within @p format.
|
||||||
|
*
|
||||||
|
* return The number of characters that would have been written to @p
|
||||||
|
* str, excluding the terminating null byte. This is greater than the
|
||||||
|
* number actually written if @p size is too small.
|
||||||
|
*/
|
||||||
|
__printf_like(3, 4)
|
||||||
|
int snprintfcb(char *str, size_t size, const char *format, ...);
|
||||||
|
|
||||||
|
/** @brief vsnprintf using Zephyrs cbprintf infrastructure.
|
||||||
|
*
|
||||||
|
* @note This function is available only when `CONFIG_CBPRINTF_LIBC_SUBSTS` is
|
||||||
|
* selected.
|
||||||
|
*
|
||||||
|
* @note The functionality of this function is significantly reduced when
|
||||||
|
* `CONFIG_CBPRINTF_NANO` is selected.
|
||||||
|
*
|
||||||
|
* @param str where the formatted content should be written
|
||||||
|
*
|
||||||
|
* @param size maximum number of chaacters for the formatted output, including
|
||||||
|
* the terminating null byte.
|
||||||
|
*
|
||||||
|
* @param format a standard ISO C format string with characters and conversion
|
||||||
|
* specifications.
|
||||||
|
*
|
||||||
|
* @param ... arguments corresponding to the conversion specifications found
|
||||||
|
* within @p format.
|
||||||
|
*
|
||||||
|
* @param ap a reference to the values to be converted.
|
||||||
|
*
|
||||||
|
* return The number of characters that would have been written to @p
|
||||||
|
* str, excluding the terminating null byte. This is greater than the
|
||||||
|
* number actually written if @p size is too small.
|
||||||
|
*/
|
||||||
|
int vsnprintfcb(char *str, size_t size, const char *format, va_list ap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_SYS_CBPRINTF_H_ */
|
|
@ -3,6 +3,7 @@
|
||||||
zephyr_sources_ifdef(CONFIG_BASE64 base64.c)
|
zephyr_sources_ifdef(CONFIG_BASE64 base64.c)
|
||||||
|
|
||||||
zephyr_sources(
|
zephyr_sources(
|
||||||
|
cbprintf.c
|
||||||
crc32_sw.c
|
crc32_sw.c
|
||||||
crc16_sw.c
|
crc16_sw.c
|
||||||
crc8_sw.c
|
crc8_sw.c
|
||||||
|
@ -23,6 +24,9 @@ zephyr_sources(
|
||||||
heap-validate.c
|
heap-validate.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
zephyr_sources_ifdef(CONFIG_CBPRINTF_COMPLETE cbprintf_complete.c)
|
||||||
|
zephyr_sources_ifdef(CONFIG_CBPRINTF_NANO cbprintf_nano.c)
|
||||||
|
|
||||||
zephyr_sources_ifdef(CONFIG_JSON_LIBRARY json.c)
|
zephyr_sources_ifdef(CONFIG_JSON_LIBRARY json.c)
|
||||||
|
|
||||||
zephyr_sources_ifdef(CONFIG_RING_BUFFER ring_buffer.c)
|
zephyr_sources_ifdef(CONFIG_RING_BUFFER ring_buffer.c)
|
||||||
|
|
|
@ -72,4 +72,6 @@ config PRINTK_SYNC
|
||||||
interleaving with concurrent usage from another CPU or an
|
interleaving with concurrent usage from another CPU or an
|
||||||
preempting interrupt.
|
preempting interrupt.
|
||||||
|
|
||||||
|
rsource "Kconfig.cbprintf"
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
117
lib/os/Kconfig.cbprintf
Normal file
117
lib/os/Kconfig.cbprintf
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# Copyright (c) 2020 Nordic Semiconductor ASA
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
choice CBPRINTF_IMPLEMENTATION
|
||||||
|
prompt "Capabilities of cbprintf implementation"
|
||||||
|
default CBPRINTF_COMPLETE
|
||||||
|
|
||||||
|
config CBPRINTF_COMPLETE
|
||||||
|
bool "All selected features"
|
||||||
|
help
|
||||||
|
Select this for an implementation that supports all potential
|
||||||
|
conversions, with Kconfig options to control availability at build
|
||||||
|
time.
|
||||||
|
|
||||||
|
# 80: -53% / 982 B (80 / 00)
|
||||||
|
config CBPRINTF_NANO
|
||||||
|
bool "Space-optimized but feature-limited"
|
||||||
|
# nano needs to count characters if it's the formatter for libc
|
||||||
|
select CBPRINTF_LIBC_SUBSTS if MINIMAL_LIBC
|
||||||
|
help
|
||||||
|
If selected a completely different implementation of the core
|
||||||
|
formatting capability is substituted. This has a much smaller code
|
||||||
|
footprint, but provides fewer capabilities.
|
||||||
|
|
||||||
|
endchoice # CBPRINTF_IMPLEMENTATION
|
||||||
|
|
||||||
|
choice CBPRINTF_INTEGRAL_CONV
|
||||||
|
prompt "Control range of convertible integer values"
|
||||||
|
default CBPRINTF_FULL_INTEGRAL
|
||||||
|
|
||||||
|
# 01: 0% / 0 B (01 / 00)
|
||||||
|
config CBPRINTF_FULL_INTEGRAL
|
||||||
|
bool "Convert the full range of integer values"
|
||||||
|
help
|
||||||
|
Build cbprintf with buffers sized to support converting the full
|
||||||
|
range of all integral and pointer values.
|
||||||
|
|
||||||
|
Selecting this has no effect on code size, but will increase call
|
||||||
|
stack size by a few words.
|
||||||
|
|
||||||
|
# 00:
|
||||||
|
config CBPRINTF_REDUCED_INTEGRAL
|
||||||
|
bool "Convert only integer values that fit in 32 bits"
|
||||||
|
help
|
||||||
|
Build cbprintf with buffers sized to support converting integer
|
||||||
|
values with no more than 32 bits.
|
||||||
|
|
||||||
|
This will decrease stack space, but affects conversion of any type
|
||||||
|
with more than 32 bits. This includes not only intmax_t but any
|
||||||
|
type that can be converted to an integral represention including
|
||||||
|
size_t and pointers.
|
||||||
|
|
||||||
|
With CBPRINTF_COMPLETE conversions that may result in value-specific
|
||||||
|
truncation are not supported, and the generated text will be the
|
||||||
|
specification (e.g. %jd).
|
||||||
|
|
||||||
|
With CBPRINTF_NANO all conversions will be attempted but values that
|
||||||
|
cannot fit will be silently truncated.
|
||||||
|
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
# 02: 82% / 1530 B (02 / 00)
|
||||||
|
config CBPRINTF_FP_SUPPORT
|
||||||
|
bool "Enable floating point formatting in cbprintf"
|
||||||
|
default y if FPU
|
||||||
|
depends on CBPRINTF_COMPLETE
|
||||||
|
help
|
||||||
|
Build the cbprintf utility function with support for floating
|
||||||
|
point format specifiers. Selecting this increases stack size
|
||||||
|
requirements slightly, but increases code size significantly.
|
||||||
|
|
||||||
|
# 04: 13% / 456 B (07 / 03)
|
||||||
|
config CBPRINTF_FP_A_SUPPORT
|
||||||
|
bool "Enable floating point %a conversions"
|
||||||
|
depends on CBPRINTF_FULL_INTEGRAL
|
||||||
|
select CBPRINTF_FP_SUPPORT
|
||||||
|
help
|
||||||
|
The %a hexadecimal format for floating point value conversion was
|
||||||
|
added in C99, but the output is not easily understood so it rarely
|
||||||
|
appears in application code.
|
||||||
|
|
||||||
|
Selecting this adds support for the conversion, but increases the
|
||||||
|
overall code size related to FP support.
|
||||||
|
|
||||||
|
# 40: -15% / -508 B (46 / 06)
|
||||||
|
config CBPRINTF_FP_ALWAYS_A
|
||||||
|
bool "Select %a format for all floating point specifications"
|
||||||
|
select CBPRINTF_FP_A_SUPPORT
|
||||||
|
help
|
||||||
|
The %a format for floats requires significantly less code than the
|
||||||
|
standard decimal representations (%f, %e, %g). Selecting this
|
||||||
|
option implicitly uses %a (or %A) for all decimal floating
|
||||||
|
conversions. The precision of the original specification is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
Selecting this decreases code size when FP_SUPPORT is enabled.
|
||||||
|
|
||||||
|
# 08: 3% / 60 B (08 / 00)
|
||||||
|
config CBPRINTF_N_SPECIFIER
|
||||||
|
bool "Support %n specifications"
|
||||||
|
depends on CBPRINTF_COMPLETE
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
If selected %n can be used to determine the number of characters
|
||||||
|
emitted. If enabled there is a small increase in code size.
|
||||||
|
|
||||||
|
# 180: 18% / 138 B (180 / 80) [NANO]
|
||||||
|
config CBPRINTF_LIBC_SUBSTS
|
||||||
|
bool "Generate C-library compatible functions using cbprintf"
|
||||||
|
help
|
||||||
|
If selected wrappers are generated for various C library functions
|
||||||
|
using the cbprintf formatter underneath. The wrappers use the C
|
||||||
|
library function name with a cb suffix; e.g. printfcb() or
|
||||||
|
vsnprintfcb().
|
||||||
|
|
||||||
|
When used with CBPRINTF_NANO this increases the implementation code
|
||||||
|
size by a small amount.
|
78
lib/os/cbprintf.c
Normal file
78
lib/os/cbprintf.c
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/cbprintf.h>
|
||||||
|
|
||||||
|
int cbprintf(cbprintf_cb out, void *ctx, const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = cbvprintf(out, ctx, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_CBPRINTF_LIBC_SUBSTS)
|
||||||
|
|
||||||
|
/* Context for sn* variants is the next space in the buffer, and the buffer
|
||||||
|
* end.
|
||||||
|
*/
|
||||||
|
struct str_ctx {
|
||||||
|
char *dp;
|
||||||
|
char *const dpe;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int str_out(int c,
|
||||||
|
void *ctx)
|
||||||
|
{
|
||||||
|
struct str_ctx *scp = ctx;
|
||||||
|
|
||||||
|
/* s*printf must return the number of characters that would be
|
||||||
|
* output, even if they don't all fit, so conditionally store
|
||||||
|
* and unconditionally succeed.
|
||||||
|
*/
|
||||||
|
if (scp->dp < scp->dpe) {
|
||||||
|
*(scp->dp++) = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int snprintfcb(char *str, size_t size, const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vsnprintfcb(str, size, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vsnprintfcb(char *str, size_t size, const char *format, va_list ap)
|
||||||
|
{
|
||||||
|
struct str_ctx ctx = {
|
||||||
|
.dp = str,
|
||||||
|
.dpe = str + size,
|
||||||
|
};
|
||||||
|
int rv = cbvprintf(str_out, &ctx, format, ap);
|
||||||
|
|
||||||
|
if (ctx.dp < ctx.dpe) {
|
||||||
|
ctx.dp[0] = 0;
|
||||||
|
} else {
|
||||||
|
ctx.dp[-1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_CBPRINTF_LIBC_SUBSTS */
|
1824
lib/os/cbprintf_complete.c
Normal file
1824
lib/os/cbprintf_complete.c
Normal file
File diff suppressed because it is too large
Load diff
326
lib/os/cbprintf_nano.c
Normal file
326
lib/os/cbprintf_nano.c
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010, 2013-2014 Wind River Systems, Inc.
|
||||||
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <sys/cbprintf.h>
|
||||||
|
#include <toolchain.h>
|
||||||
|
#include <linker/sections.h>
|
||||||
|
#include <syscall_handler.h>
|
||||||
|
#include <logging/log.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
enum pad_type {
|
||||||
|
PAD_NONE,
|
||||||
|
PAD_ZERO_BEFORE,
|
||||||
|
PAD_SPACE_BEFORE,
|
||||||
|
PAD_SPACE_AFTER,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_CBPRINTF_FULL_INTEGRAL
|
||||||
|
typedef intmax_t int_value_type;
|
||||||
|
typedef uintmax_t uint_value_type;
|
||||||
|
#else
|
||||||
|
typedef int32_t int_value_type;
|
||||||
|
typedef uint32_t uint_value_type;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum number of digits in a printed decimal value (hex is always
|
||||||
|
* less, obviously). Funny formula produces 10 max digits for 32 bit,
|
||||||
|
* 21 for 64. It may be incorrect for other value lengths.
|
||||||
|
*/
|
||||||
|
#define DIGITS_BUFLEN (11U * (sizeof(uint_value_type) / 4U) - 1U)
|
||||||
|
|
||||||
|
BUILD_ASSERT(sizeof(uint_value_type) <= 8U,
|
||||||
|
"DIGITS_BUFLEN formula may be incorrect");
|
||||||
|
|
||||||
|
static void print_digits(cbprintf_cb out, void *ctx, uint_value_type num,
|
||||||
|
unsigned int base, bool pad_before, char pad_char,
|
||||||
|
int min_width, size_t *countp)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
char buf[DIGITS_BUFLEN];
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/* Print it backwards into the end of the buffer, low digits first */
|
||||||
|
for (i = DIGITS_BUFLEN - 1U; num != 0U; i--) {
|
||||||
|
buf[i] = "0123456789abcdef"[num % base];
|
||||||
|
num /= base;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == DIGITS_BUFLEN - 1U) {
|
||||||
|
buf[i] = '0';
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pad = MAX(min_width - (int)(DIGITS_BUFLEN - i), 0);
|
||||||
|
|
||||||
|
for (/**/; pad > 0 && pad_before; pad--) {
|
||||||
|
out(pad_char, ctx);
|
||||||
|
if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (/**/; i < DIGITS_BUFLEN; i++) {
|
||||||
|
out(buf[i], ctx);
|
||||||
|
if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (/**/; pad > 0; pad--) {
|
||||||
|
out(pad_char, ctx);
|
||||||
|
if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) {
|
||||||
|
*countp += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_hex(cbprintf_cb out, void *ctx, uint_value_type num,
|
||||||
|
enum pad_type padding, int min_width, size_t *count)
|
||||||
|
{
|
||||||
|
print_digits(out, ctx, num, 16U, padding != PAD_SPACE_AFTER,
|
||||||
|
padding == PAD_ZERO_BEFORE ? '0' : ' ', min_width,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_dec(cbprintf_cb out, void *ctx, uint_value_type num,
|
||||||
|
enum pad_type padding, int min_width, size_t *count)
|
||||||
|
{
|
||||||
|
print_digits(out, ctx, num, 10U, padding != PAD_SPACE_AFTER,
|
||||||
|
padding == PAD_ZERO_BEFORE ? '0' : ' ', min_width,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ok64(cbprintf_cb out, void *ctx, long long val, size_t *count)
|
||||||
|
{
|
||||||
|
if (sizeof(int_value_type) < 8U && val != (int_value_type) val) {
|
||||||
|
out('E', ctx);
|
||||||
|
out('R', ctx);
|
||||||
|
out('R', ctx);
|
||||||
|
if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) {
|
||||||
|
*count += 3;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool negative(uint_value_type val)
|
||||||
|
{
|
||||||
|
const uint_value_type hibit = ~(((uint_value_type) ~1) >> 1U);
|
||||||
|
|
||||||
|
return (val & hibit) != 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Printk internals
|
||||||
|
*
|
||||||
|
* See printk() for description.
|
||||||
|
* @param fmt Format string
|
||||||
|
* @param ap Variable parameters
|
||||||
|
*
|
||||||
|
* @return N/A
|
||||||
|
*/
|
||||||
|
int cbvprintf(cbprintf_cb out, void *ctx, const char *fmt, va_list ap)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
int might_format = 0; /* 1 if encountered a '%' */
|
||||||
|
enum pad_type padding = PAD_NONE;
|
||||||
|
int min_width = -1;
|
||||||
|
char length_mod = 0;
|
||||||
|
|
||||||
|
#define OUTC(_c) do { \
|
||||||
|
out((int)(_c), ctx); \
|
||||||
|
if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) { \
|
||||||
|
++count; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
/* fmt has already been adjusted if needed */
|
||||||
|
while (*fmt) {
|
||||||
|
if (!might_format) {
|
||||||
|
if (*fmt != '%') {
|
||||||
|
OUTC(*fmt);
|
||||||
|
} else {
|
||||||
|
might_format = 1;
|
||||||
|
min_width = -1;
|
||||||
|
padding = PAD_NONE;
|
||||||
|
length_mod = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (*fmt) {
|
||||||
|
case '-':
|
||||||
|
padding = PAD_SPACE_AFTER;
|
||||||
|
goto still_might_format;
|
||||||
|
case '0':
|
||||||
|
if (min_width < 0 && padding == PAD_NONE) {
|
||||||
|
padding = PAD_ZERO_BEFORE;
|
||||||
|
goto still_might_format;
|
||||||
|
}
|
||||||
|
__fallthrough;
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
if (min_width < 0) {
|
||||||
|
min_width = *fmt - '0';
|
||||||
|
} else {
|
||||||
|
min_width = 10 * min_width + *fmt - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (padding == PAD_NONE) {
|
||||||
|
padding = PAD_SPACE_BEFORE;
|
||||||
|
}
|
||||||
|
goto still_might_format;
|
||||||
|
case 'h':
|
||||||
|
case 'l':
|
||||||
|
case 'z':
|
||||||
|
if (*fmt == 'h' && length_mod == 'h') {
|
||||||
|
length_mod = 'H';
|
||||||
|
} else if (*fmt == 'l' && length_mod == 'l') {
|
||||||
|
length_mod = 'L';
|
||||||
|
} else if (length_mod == 0) {
|
||||||
|
length_mod = *fmt;
|
||||||
|
} else {
|
||||||
|
OUTC('%');
|
||||||
|
OUTC(*fmt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
goto still_might_format;
|
||||||
|
case 'd':
|
||||||
|
case 'i':
|
||||||
|
case 'u': {
|
||||||
|
uint_value_type d;
|
||||||
|
|
||||||
|
if (length_mod == 'z') {
|
||||||
|
d = va_arg(ap, ssize_t);
|
||||||
|
} else if (length_mod == 'l') {
|
||||||
|
d = va_arg(ap, long);
|
||||||
|
} else if (length_mod == 'L') {
|
||||||
|
|
||||||
|
long long lld = va_arg(ap,
|
||||||
|
long long);
|
||||||
|
if (!ok64(out, ctx, lld, &count)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
d = (uint_value_type) lld;
|
||||||
|
} else if (*fmt == 'u') {
|
||||||
|
d = va_arg(ap, unsigned int);
|
||||||
|
} else {
|
||||||
|
d = va_arg(ap, int);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*fmt != 'u' && negative(d)) {
|
||||||
|
OUTC('-');
|
||||||
|
d = -d;
|
||||||
|
min_width--;
|
||||||
|
}
|
||||||
|
print_dec(out, ctx, d, padding, min_width,
|
||||||
|
&count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'p':
|
||||||
|
case 'x':
|
||||||
|
case 'X': {
|
||||||
|
uint_value_type x;
|
||||||
|
|
||||||
|
if (*fmt == 'p') {
|
||||||
|
const char *cp;
|
||||||
|
|
||||||
|
x = (uintptr_t)va_arg(ap, void *);
|
||||||
|
if (x == 0) {
|
||||||
|
cp = "(nil)";
|
||||||
|
} else {
|
||||||
|
cp = "0x";
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*cp) {
|
||||||
|
OUTC(*cp++);
|
||||||
|
}
|
||||||
|
if (x == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (length_mod == 'l') {
|
||||||
|
x = va_arg(ap, unsigned long);
|
||||||
|
} else if (length_mod == 'L') {
|
||||||
|
x = va_arg(ap, unsigned long long);
|
||||||
|
} else {
|
||||||
|
x = va_arg(ap, unsigned int);
|
||||||
|
}
|
||||||
|
|
||||||
|
print_hex(out, ctx, x, padding, min_width,
|
||||||
|
&count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 's': {
|
||||||
|
char *s = va_arg(ap, char *);
|
||||||
|
char *start = s;
|
||||||
|
|
||||||
|
while (*s) {
|
||||||
|
OUTC(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (padding == PAD_SPACE_AFTER) {
|
||||||
|
int remaining = min_width - (s - start);
|
||||||
|
|
||||||
|
while (remaining-- > 0) {
|
||||||
|
OUTC(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'c': {
|
||||||
|
int c = va_arg(ap, int);
|
||||||
|
|
||||||
|
OUTC(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '%': {
|
||||||
|
OUTC('%');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
OUTC('%');
|
||||||
|
OUTC(*fmt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
might_format = 0;
|
||||||
|
}
|
||||||
|
still_might_format:
|
||||||
|
++fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbprintf_arglen(const char *format)
|
||||||
|
{
|
||||||
|
size_t rv = 0;
|
||||||
|
bool last_pct = false;
|
||||||
|
|
||||||
|
while (*format != 0) {
|
||||||
|
if (*format == '%') {
|
||||||
|
last_pct = !last_pct;
|
||||||
|
} else if (last_pct) {
|
||||||
|
++rv;
|
||||||
|
last_pct = false;
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
++format;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue