/* * Copyright (c) 2010, 2013-2014 Wind River Systems, Inc. * Copyright (c) 2020 Nordic Semiconductor ASA * Copyright (c) 2021 BayLibre, SAS * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #ifdef CONFIG_CBPRINTF_FULL_INTEGRAL typedef intmax_t int_value_type; typedef uintmax_t uint_value_type; #define DIGITS_BUFLEN 21 BUILD_ASSERT(sizeof(uint_value_type) <= 8U, "DIGITS_BUFLEN may be incorrect"); #else typedef int32_t int_value_type; typedef uint32_t uint_value_type; #define DIGITS_BUFLEN 10 #endif #define ALPHA(fmt) (((fmt) & 0x60) - '0' - 10 + 1) /* Convert value to string, storing characters downwards */ static inline int convert_value(uint_value_type num, unsigned int base, unsigned int alpha, char *buftop) { int i = 0; do { unsigned int c = num % base; if (c >= 10) { c += alpha; } buftop[i--] = c + '0'; num /= base; } while (num); return -i; } #define OUTC(_c) do { \ out((int)(_c), ctx); \ if (IS_ENABLED(CONFIG_CBPRINTF_LIBC_SUBSTS)) { \ ++count; \ } \ } while (false) #define PAD_ZERO BIT(0) #define PAD_TAIL BIT(1) /* Skip over the argument tag if needed as it is not being used here. */ #define SKIP_TAG_IF_NEEDED(ap, tagged_ap) \ do { \ if (IS_ENABLED(CONFIG_CBPRINTF_PACKAGE_SUPPORT_TAGGED_ARGUMENTS) \ && tagged_ap) { \ (void)va_arg(ap, int); \ } \ } while (0) /** * @brief Printk internals * * See printk() for description. * @param fmt Format string * @param ap Variable parameters * * @return printed byte count if CONFIG_CBPRINTF_LIBC_SUBSTS is set */ int z_cbvprintf_impl(cbprintf_cb out, void *ctx, const char *fmt, va_list ap, uint32_t flags) { size_t count = 0; char buf[DIGITS_BUFLEN]; char *prefix, *data; int min_width, precision, data_len; char padding_mode, length_mod, special; const bool tagged_ap = (flags & Z_CBVPRINTF_PROCESS_FLAG_TAGGED_ARGS) == Z_CBVPRINTF_PROCESS_FLAG_TAGGED_ARGS; /* we pre-increment in the loop afterwards */ fmt--; start: while (*++fmt != '%') { if (*fmt == '\0') { return count; } OUTC(*fmt); } min_width = -1; precision = -1; prefix = ""; padding_mode = 0; length_mod = 0; special = 0; for (fmt++ ; ; fmt++) { switch (*fmt) { case 0: return count; case '%': OUTC('%'); goto start; case '-': padding_mode = PAD_TAIL; continue; case '.': precision = 0; padding_mode &= (char)~PAD_ZERO; continue; case '0': if (min_width < 0 && precision < 0 && !padding_mode) { padding_mode = PAD_ZERO; continue; } __fallthrough; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (precision >= 0) { precision = 10 * precision + *fmt - '0'; } else { if (min_width < 0) { min_width = 0; } min_width = 10 * min_width + *fmt - '0'; } continue; case '*': SKIP_TAG_IF_NEEDED(ap, tagged_ap); if (precision >= 0) { precision = va_arg(ap, int); } else { min_width = va_arg(ap, int); if (min_width < 0) { min_width = -min_width; padding_mode = PAD_TAIL; } } continue; case '+': case ' ': case '#': special = *fmt; continue; 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); goto start; } continue; case 'd': case 'i': case 'u': { uint_value_type d; SKIP_TAG_IF_NEEDED(ap, tagged_ap); if (length_mod == 'z') { if (*fmt == 'u') { d = va_arg(ap, size_t); } else { d = va_arg(ap, ssize_t); } } else if (length_mod == 'l') { if (*fmt == 'u') { d = va_arg(ap, unsigned long); } else { d = va_arg(ap, long); } } else if (length_mod == 'L') { if (*fmt == 'u') { unsigned long long llu = va_arg(ap, unsigned long long); if (llu != (uint_value_type) llu) { data = "ERR"; data_len = 3; precision = 0; break; } d = (uint_value_type) llu; } else { long long lld = va_arg(ap, long long); if (lld != (int_value_type) lld) { data = "ERR"; data_len = 3; precision = 0; break; } d = (int_value_type) lld; } } else if (*fmt == 'u') { d = va_arg(ap, unsigned int); } else { d = va_arg(ap, int); } if (*fmt != 'u' && (int_value_type)d < 0) { d = -d; prefix = "-"; min_width--; } else if (special == ' ') { prefix = " "; min_width--; } else if (special == '+') { prefix = "+"; min_width--; } else { ; } data_len = convert_value(d, 10, 0, buf + sizeof(buf) - 1); data = buf + sizeof(buf) - data_len; break; } case 'p': case 'x': case 'X': { uint_value_type x; SKIP_TAG_IF_NEEDED(ap, tagged_ap); if (*fmt == 'p') { x = (uintptr_t)va_arg(ap, void *); if (x == (uint_value_type)0) { data = "(nil)"; data_len = 5; precision = 0; break; } special = '#'; } else if (length_mod == 'l') { x = va_arg(ap, unsigned long); } else if (length_mod == 'L') { unsigned long long llx = va_arg(ap, unsigned long long); if (llx != (uint_value_type) llx) { data = "ERR"; data_len = 3; precision = 0; break; } x = (uint_value_type) llx; } else { x = va_arg(ap, unsigned int); } if (special == '#') { prefix = (*fmt & 0x20) ? "0x" : "0X"; min_width -= 2; } data_len = convert_value(x, 16, ALPHA(*fmt), buf + sizeof(buf) - 1); data = buf + sizeof(buf) - data_len; break; } case 's': { SKIP_TAG_IF_NEEDED(ap, tagged_ap); data = va_arg(ap, char *); data_len = strlen(data); if (precision >= 0 && data_len > precision) { data_len = precision; } precision = 0; break; } case 'c': { int c; SKIP_TAG_IF_NEEDED(ap, tagged_ap); c = va_arg(ap, int); buf[0] = c; data = buf; data_len = 1; precision = 0; break; } default: OUTC('%'); OUTC(*fmt); goto start; } if (precision < 0 && (padding_mode & PAD_ZERO)) { precision = min_width; } min_width -= data_len; precision -= data_len; if (precision > 0) { min_width -= precision; } if (!(padding_mode & PAD_TAIL)) { while (--min_width >= 0) { OUTC(' '); } } while (*prefix) { OUTC(*prefix++); } while (--precision >= 0) { OUTC('0'); } while (--data_len >= 0) { OUTC(*data++); } while (--min_width >= 0) { OUTC(' '); } goto start; } }