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
|
||||
display/index.rst
|
||||
file_system/index.rst
|
||||
misc/formatted_output.rst
|
||||
kernel/index.rst
|
||||
logging/index.rst
|
||||
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(
|
||||
cbprintf.c
|
||||
crc32_sw.c
|
||||
crc16_sw.c
|
||||
crc8_sw.c
|
||||
|
@ -23,6 +24,9 @@ zephyr_sources(
|
|||
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_RING_BUFFER ring_buffer.c)
|
||||
|
|
|
@ -72,4 +72,6 @@ config PRINTK_SYNC
|
|||
interleaving with concurrent usage from another CPU or an
|
||||
preempting interrupt.
|
||||
|
||||
rsource "Kconfig.cbprintf"
|
||||
|
||||
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