Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: add cbprintf capability #29876

Merged
merged 11 commits into from
Nov 13, 2020
1 change: 1 addition & 0 deletions doc/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions doc/reference/misc/formatted_output.rst
Original file line number Diff line number Diff line change
@@ -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 changes: 177 additions & 0 deletions include/sys/cbprintf.h
Original file line number Diff line number Diff line change
@@ -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 changes: 0 additions & 3 deletions include/sys/printk.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ extern __printf_like(3, 4) int snprintk(char *str, size_t size,
extern __printf_like(3, 0) int vsnprintk(char *str, size_t size,
const char *fmt, va_list ap);

extern __printf_like(3, 0) void z_vprintk(int (*out)(int f, void *c), void *ctx,
const char *fmt, va_list ap);

#ifdef __cplusplus
}
#endif
Expand Down
8 changes: 7 additions & 1 deletion include/sys/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,13 @@ uint8_t u8_to_dec(char *buf, uint8_t buflen, uint8_t value);
* @brief Bit mask with bits 0 through <tt>n-1</tt> (inclusive) set,
* or 0 if @p n is 0.
*/
#define BIT_MASK(n) (BIT(n) - 1)
#define BIT_MASK(n) (BIT(n) - 1UL)

/**
* @brief 64-bit bit mask with bits 0 through <tt>n-1</tt> (inclusive) set,
* or 0 if @p n is 0.
*/
#define BIT64_MASK(n) (BIT64(n) - 1ULL)

/**
* @}
Expand Down
4 changes: 4 additions & 0 deletions lib/libc/minimal/include/stdint.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extern "C" {
#define INT16_MAX __INT16_MAX__
#define INT32_MAX __INT32_MAX__
#define INT64_MAX __INT64_MAX__
#define INTMAX_MAX __INT64_MAX__

#define INT8_MIN (-INT8_MAX - 1)
#define INT16_MIN (-INT16_MAX - 1)
Expand All @@ -27,6 +28,7 @@ extern "C" {
#define UINT16_MAX __UINT16_MAX__
#define UINT32_MAX __UINT32_MAX__
#define UINT64_MAX __UINT64_MAX__
#define UINTMAX_MAX __UINT64_MAX__

#define INTPTR_MAX __INTPTR_MAX__
#define INTPTR_MIN (-INTPTR_MAX - 1)
Expand All @@ -41,6 +43,7 @@ typedef __INT8_TYPE__ int8_t;
typedef __INT16_TYPE__ int16_t;
typedef __INT32_TYPE__ int32_t;
typedef __INT64_TYPE__ int64_t;
typedef __INT64_TYPE__ intmax_t;

typedef __INT_FAST8_TYPE__ int_fast8_t;
typedef __INT_FAST16_TYPE__ int_fast16_t;
Expand All @@ -56,6 +59,7 @@ typedef __UINT8_TYPE__ uint8_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __UINT64_TYPE__ uint64_t;
typedef __UINT64_TYPE__ uintmax_t;

typedef __UINT_FAST8_TYPE__ uint_fast8_t;
typedef __UINT_FAST16_TYPE__ uint_fast16_t;
Expand Down
12 changes: 5 additions & 7 deletions lib/libc/minimal/source/stdout/fprintf.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@

#include <stdarg.h>
#include <stdio.h>
#include <sys/cbprintf.h>

#define DESC(d) ((void *)d)

extern int z_prf(int (*func)(), void *dest,
const char *format, va_list vargs);

int fprintf(FILE *_MLIBC_RESTRICT F, const char *_MLIBC_RESTRICT format, ...)
{
va_list vargs;
int r;

va_start(vargs, format);
r = z_prf(fputc, DESC(F), format, vargs);
r = cbvprintf(fputc, DESC(F), format, vargs);
va_end(vargs);

return r;
Expand All @@ -31,7 +29,7 @@ int vfprintf(FILE *_MLIBC_RESTRICT F, const char *_MLIBC_RESTRICT format,
{
int r;

r = z_prf(fputc, DESC(F), format, vargs);
r = cbvprintf(fputc, DESC(F), format, vargs);

return r;
}
Expand All @@ -42,7 +40,7 @@ int printf(const char *_MLIBC_RESTRICT format, ...)
int r;

va_start(vargs, format);
r = z_prf(fputc, DESC(stdout), format, vargs);
r = cbvprintf(fputc, DESC(stdout), format, vargs);
va_end(vargs);

return r;
Expand All @@ -52,7 +50,7 @@ int vprintf(const char *_MLIBC_RESTRICT format, va_list vargs)
{
int r;

r = z_prf(fputc, DESC(stdout), format, vargs);
r = cbvprintf(fputc, DESC(stdout), format, vargs);

return r;
}
12 changes: 5 additions & 7 deletions lib/libc/minimal/source/stdout/sprintf.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@

#include <stdarg.h>
#include <stdio.h>

extern int z_prf(int (*func)(), void *dest,
const char *format, va_list vargs);
#include <sys/cbprintf.h>

struct emitter {
char *ptr;
Expand Down Expand Up @@ -44,7 +42,7 @@ int snprintf(char *_MLIBC_RESTRICT s, size_t len,
p.len = (int) len;

va_start(vargs, format);
r = z_prf(sprintf_out, (void *) (&p), format, vargs);
r = cbvprintf(sprintf_out, (void *) (&p), format, vargs);
va_end(vargs);

*(p.ptr) = 0;
Expand All @@ -62,7 +60,7 @@ int sprintf(char *_MLIBC_RESTRICT s, const char *_MLIBC_RESTRICT format, ...)
p.len = (int) 0x7fffffff; /* allow up to "maxint" characters */

va_start(vargs, format);
r = z_prf(sprintf_out, (void *) (&p), format, vargs);
r = cbvprintf(sprintf_out, (void *) (&p), format, vargs);
va_end(vargs);

*(p.ptr) = 0;
Expand All @@ -83,7 +81,7 @@ int vsnprintf(char *_MLIBC_RESTRICT s, size_t len,
p.ptr = s;
p.len = (int) len;

r = z_prf(sprintf_out, (void *) (&p), format, vargs);
r = cbvprintf(sprintf_out, (void *) (&p), format, vargs);

*(p.ptr) = 0;
return r;
Expand All @@ -98,7 +96,7 @@ int vsprintf(char *_MLIBC_RESTRICT s, const char *_MLIBC_RESTRICT format,
p.ptr = s;
p.len = (int) 0x7fffffff; /* allow up to "maxint" characters */

r = z_prf(sprintf_out, (void *) (&p), format, vargs);
r = cbvprintf(sprintf_out, (void *) (&p), format, vargs);

*(p.ptr) = 0;
return r;
Expand Down
Loading