| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/runtime/print.h" |
| |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "sw/device/lib/base/memory.h" |
| |
| // This is declared as an enum to force the values to be |
| // compile-time constants, but the type is otherwise not |
| // used for anything. |
| enum { |
| // Standard format specifiers. |
| kPercent = '%', |
| kCharacter = 'c', |
| kString = 's', |
| kSignedDec1 = 'd', |
| kSignedDec2 = 'i', |
| kUnsignedOct = 'o', |
| kUnsignedHexLow = 'x', |
| kUnsignedHexHigh = 'X', |
| kUnsignedDec = 'u', |
| kPointer = 'p', |
| |
| // Verilog-style format specifiers. |
| kSvBinary = 'b', |
| kSvHexLow = 'h', |
| kSvHexHigh = 'H', |
| |
| // Other non-standard specifiers. |
| kSizedStr = 'z', |
| }; |
| |
| // NOTE: all of the lengths of the strings below are given so that the NUL |
| // terminator is left off; that way, `sizeof(kConst)` does not include it. |
| static const char kDigitsLow[16] = "0123456789abcdef"; |
| static const char kDigitsHigh[16] = "0123456789ABCDEF"; |
| |
| static const char kErrorNul[17] = "%<unexpected nul>"; |
| static const char kUnknownSpec[15] = "%<unknown spec>"; |
| static const char kErrorTooWide[12] = "%<bad width>"; |
| |
| static size_t base_dev_null(void *data, const char *buf, size_t len) { |
| return len; |
| } |
| static buffer_sink_t base_stdout = { |
| .data = NULL, .sink = &base_dev_null, |
| }; |
| |
| void base_set_stdout(buffer_sink_t out) { |
| if (out.sink == NULL) { |
| out.sink = &base_dev_null; |
| } |
| base_stdout = out; |
| } |
| |
| static size_t base_dev_uart(void *data, const char *buf, size_t len) { |
| const dif_uart_t *uart = (const dif_uart_t *)data; |
| for (size_t i = 0; i < len; ++i) { |
| if (dif_uart_byte_send_polled(uart, (uint8_t)buf[i]) != kDifUartOk) { |
| return i; |
| } |
| } |
| return len; |
| } |
| |
| void base_uart_stdout(const dif_uart_t *uart) { |
| base_set_stdout( |
| (buffer_sink_t){.data = (void *)uart, .sink = &base_dev_uart}); |
| } |
| |
| size_t base_printf(const char *format, ...) { |
| va_list args; |
| va_start(args, format); |
| size_t bytes_left = base_vprintf(format, args); |
| va_end(args); |
| return bytes_left; |
| } |
| |
| size_t base_vprintf(const char *format, va_list args) { |
| return base_vfprintf(base_stdout, format, args); |
| } |
| |
| typedef struct snprintf_captures_t { |
| char *buf; |
| size_t bytes_left; |
| } snprintf_captures_t; |
| |
| static size_t snprintf_sink(void *data, const char *buf, size_t len) { |
| snprintf_captures_t *captures = (snprintf_captures_t *)data; |
| if (captures->bytes_left == 0) { |
| return 0; |
| } |
| |
| if (len > captures->bytes_left) { |
| len = captures->bytes_left; |
| } |
| memcpy(captures->buf, buf, len); |
| captures->buf += len; |
| captures->bytes_left -= len; |
| return len; |
| } |
| |
| size_t base_snprintf(char *buf, size_t len, const char *format, ...) { |
| va_list args; |
| va_start(args, format); |
| |
| snprintf_captures_t data = { |
| .buf = buf, .bytes_left = len, |
| }; |
| buffer_sink_t out = { |
| .data = &data, .sink = &snprintf_sink, |
| }; |
| size_t bytes_left = base_vfprintf(out, format, args); |
| va_end(args); |
| return bytes_left; |
| } |
| |
| size_t base_fprintf(buffer_sink_t out, const char *format, ...) { |
| va_list args; |
| va_start(args, format); |
| size_t bytes_left = base_vfprintf(out, format, args); |
| va_end(args); |
| return bytes_left; |
| } |
| |
| /** |
| * Consumes characters from `format` until a '%' or NUL is reached. All |
| * characters seen before that are then sinked into `out`. |
| * |
| * @param out the sink to write bytes to. |
| * @param format a pointer to the format string to consume a prefix of. |
| * @param[out] bytes_written out param for the number of bytes writen to `out`. |
| * @return true if an unprocessed '%' was found. |
| */ |
| static bool consume_until_percent(buffer_sink_t out, const char **format, |
| size_t *bytes_written) { |
| size_t text_len = 0; |
| while (true) { |
| char c = (*format)[text_len]; |
| if (c == '\0' || c == kPercent) { |
| if (text_len > 0) { |
| *bytes_written += out.sink(out.data, *format, text_len); |
| } |
| *format += text_len; |
| return c != '\0'; |
| } |
| ++text_len; |
| } |
| } |
| |
| /** |
| * Represents a parsed format specifier. |
| */ |
| typedef struct format_specifier { |
| char type; |
| size_t width; |
| } format_specifier_t; |
| |
| /** |
| * Consumes characters from `format` until a complete format specifier is |
| * parsed. See the documentation in `print.h` for full syntax. |
| * |
| * @param out the sink to write bytes to. |
| * @param format a pointer to the format string to consume a prefix of. |
| * @param[out] spec out param for the specifier. |
| * @return whether the parse succeeded. |
| */ |
| static bool consume_format_specifier(buffer_sink_t out, const char **format, |
| size_t *bytes_written, |
| format_specifier_t *spec) { |
| *spec = (format_specifier_t){0}; |
| |
| // Consume the percent sign. |
| ++(*format); |
| |
| // Attempt to parse out an unsigned, decimal number, a "width", |
| // after the percent sign; the format specifier is the character |
| // immediately after this width. |
| size_t spec_len = 0; |
| bool has_width = false; |
| while (true) { |
| char c = (*format)[spec_len]; |
| if (c == '\0') { |
| *bytes_written += out.sink(out.data, kErrorNul, sizeof(kErrorNul)); |
| return false; |
| } |
| if (c < '0' || c > '9') { |
| break; |
| } |
| has_width = true; |
| spec->width *= 10; |
| spec->width += (c - '0'); |
| ++spec_len; |
| } |
| |
| if ((spec->width == 0 && has_width) || spec->width > 32) { |
| *bytes_written += out.sink(out.data, kErrorTooWide, sizeof(kErrorTooWide)); |
| return false; |
| } |
| |
| spec->type = (*format)[spec_len]; |
| *format += spec_len + 1; |
| return true; |
| } |
| |
| /** |
| * Write the digits of `value` onto `out`. |
| * |
| * @param out the sink to write bytes to. |
| * @param value the value to "stringify". |
| * @param width the minimum width to print; going below will result in writing |
| * out zeroes. |
| * @param base the base to express `value` in. |
| * @param glyphs an array of characters to use as the digits of a number, which |
| * should be at least ast long as `base`. |
| * @return the number of bytes written. |
| */ |
| static size_t write_digits(buffer_sink_t out, uint32_t value, uint32_t width, |
| uint32_t base, const char *glyphs) { |
| // All allocations are done relative to a buffer that could hold the longest |
| // textual representation of a number: ~0x0 in base 2, i.e., 32 ones. |
| static const int kWordBits = sizeof(uint32_t) * 8; |
| char buffer[kWordBits]; |
| |
| size_t len = 0; |
| while (value > 0) { |
| uint32_t digit = value % base; |
| value /= base; |
| buffer[kWordBits - 1 - len] = glyphs[digit]; |
| ++len; |
| } |
| width = width == 0 ? 1 : width; |
| width = width > kWordBits ? kWordBits : width; |
| while (len < width) { |
| buffer[kWordBits - len - 1] = '0'; |
| ++len; |
| } |
| return out.sink(out.data, buffer + (kWordBits - len), len); |
| } |
| |
| /** |
| * Prints out the next entry in `args` according to `spec`. |
| * |
| * This function assumes that `spec` accurately describes the next entry in |
| * `args`. |
| * |
| * @param out the sink to write bytes to. |
| * @param spec the specifier to use for stringifying. |
| * @param[out] bytes_written out param for the number of bytes writen to `out`. |
| * @param args the list to pull an entry from. |
| */ |
| static void process_specifier(buffer_sink_t out, format_specifier_t spec, |
| size_t *bytes_written, va_list *args) { |
| // Switch on the specifier. At this point, we assert that there is |
| // an initialized value of correct type in the VA list; if it is |
| // missing, the caller has caused UB. |
| switch (spec.type) { |
| case kPercent: { |
| *bytes_written += out.sink(out.data, "%", 1); |
| break; |
| } |
| case kCharacter: { |
| char value = (char)va_arg(*args, uint32_t); |
| *bytes_written += out.sink(out.data, &value, 1); |
| break; |
| } |
| case kString: { |
| char *value = va_arg(*args, char *); |
| size_t len = 0; |
| while (value[len] != '\0') { |
| ++len; |
| } |
| *bytes_written += out.sink(out.data, value, len); |
| break; |
| } |
| case kSizedStr: { |
| size_t len = va_arg(*args, size_t); |
| char *value = va_arg(*args, char *); |
| *bytes_written += out.sink(out.data, value, len); |
| break; |
| } |
| case kSignedDec1: |
| case kSignedDec2: { |
| uint32_t value = va_arg(*args, uint32_t); |
| if (((int32_t)value) < 0) { |
| *bytes_written += out.sink(out.data, "-", 1); |
| value = -value; |
| } |
| *bytes_written += write_digits(out, value, spec.width, 10, kDigitsLow); |
| break; |
| } |
| case kUnsignedOct: { |
| uint32_t value = va_arg(*args, uint32_t); |
| *bytes_written += write_digits(out, value, spec.width, 8, kDigitsLow); |
| break; |
| } |
| case kPointer: { |
| // Pointers are formatted as 0x<hex digits>, where the width is always |
| // set to the number necessary to represent a pointer on the current |
| // platform, that is, the size of uintptr_t in nybbles. For example, on |
| // different architecutres the null pointer prints as |
| // - rv32imc: 0x00000000 (four bytes, eight nybbles). |
| // - amd64: 0x0000000000000000 (eight bytes, sixteen nybbles). |
| *bytes_written += out.sink(out.data, "0x", 2); |
| uintptr_t value = va_arg(*args, uintptr_t); |
| *bytes_written += |
| write_digits(out, value, sizeof(uintptr_t) * 2, 16, kDigitsLow); |
| break; |
| } |
| case kSvHexLow: |
| case kUnsignedHexLow: { |
| uint32_t value = va_arg(*args, uint32_t); |
| *bytes_written += write_digits(out, value, spec.width, 16, kDigitsLow); |
| break; |
| } |
| case kSvHexHigh: |
| case kUnsignedHexHigh: { |
| uint32_t value = va_arg(*args, uint32_t); |
| *bytes_written += write_digits(out, value, spec.width, 16, kDigitsHigh); |
| break; |
| } |
| case kUnsignedDec: { |
| uint32_t value = va_arg(*args, uint32_t); |
| *bytes_written += write_digits(out, value, spec.width, 10, kDigitsLow); |
| break; |
| } |
| case kSvBinary: { |
| uint32_t value = va_arg(*args, uint32_t); |
| *bytes_written += write_digits(out, value, spec.width, 2, kDigitsLow); |
| break; |
| } |
| default: { |
| *bytes_written += out.sink(out.data, kUnknownSpec, sizeof(kUnknownSpec)); |
| } |
| } |
| } |
| |
| size_t base_vfprintf(buffer_sink_t out, const char *format, va_list args) { |
| if (out.sink == NULL) { |
| out.sink = &base_dev_null; |
| } |
| |
| // NOTE: This copy is necessary on amd64 and other platforms, where |
| // `va_list` is a fixed array type (and, as such, decays to a pointer in |
| // an argument list). On PSABI RV32IMC, however, `va_list` is a `void*`, so |
| // this is a copy of the pointer, not the array. |
| va_list args_copy; |
| va_copy(args_copy, args); |
| |
| size_t bytes_written = 0; |
| while (format[0] != '\0') { |
| if (!consume_until_percent(out, &format, &bytes_written)) { |
| break; |
| } |
| format_specifier_t spec; |
| if (!consume_format_specifier(out, &format, &bytes_written, &spec)) { |
| break; |
| } |
| |
| process_specifier(out, spec, &bytes_written, &args_copy); |
| } |
| |
| va_end(args_copy); |
| return bytes_written; |
| } |