| // 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/macros.h" | |
| #include "sw/device/lib/base/memory.h" | |
| #include "sw/device/lib/base/status.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. | |
| kHexLeLow = 'y', | |
| kHexLeHigh = 'Y', | |
| kStatusResult = 'r', | |
| }; | |
| // 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, | |
| // Note: Using `&base_dev_null` causes this variable to be placed in the | |
| // .data section and triggers the assertion in rom.ld. | |
| .sink = 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]) != kDifOk) { | |
| 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; | |
| char padding; | |
| bool is_nonstd; | |
| } 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); | |
| // Parse a ! to detect an OpenTitan-specific extension (that isn't one | |
| // of the Verilog ones...). | |
| if ((*format)[0] == '!') { | |
| spec->is_nonstd = true; | |
| ++(*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. | |
| // | |
| // `spec->padding` is used to indicate whether we've seen a width; | |
| // if nonzero, we have an actual width. | |
| size_t spec_len = 0; | |
| 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; | |
| } | |
| if (spec->padding == 0) { | |
| if (c == '0') { | |
| spec->padding = '0'; | |
| ++spec_len; | |
| continue; | |
| } | |
| spec->padding = ' '; | |
| } | |
| spec->width *= 10; | |
| spec->width += (c - '0'); | |
| ++spec_len; | |
| } | |
| if ((spec->width == 0 && spec->padding != 0) || 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, | |
| char padding, 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; | |
| if (value == 0) { | |
| buffer[kWordBits - 1] = glyphs[0]; | |
| ++len; | |
| } | |
| 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] = padding; | |
| ++len; | |
| } | |
| return out.sink(out.data, buffer + (kWordBits - len), len); | |
| } | |
| static size_t write_status(buffer_sink_t out, status_t value, bool as_json) { | |
| // The module id is defined to be 3 chars long. | |
| char mod[] = {'"', 0, 0, 0, '"', ','}; | |
| int32_t arg; | |
| const char *start; | |
| bool err = status_extract(value, &start, &arg, &mod[1]); | |
| // strlen of the status code. | |
| const char *end = start; | |
| while (*end) | |
| end++; | |
| size_t len = 0; | |
| len += out.sink(out.data, "{\"", as_json ? 2 : 0); | |
| len += out.sink(out.data, start, end - start); | |
| len += out.sink(out.data, "\"", as_json ? 1 : 0); | |
| len += out.sink(out.data, ":", 1); | |
| if (err) { | |
| // All error codes include the module identifier. | |
| len += out.sink(out.data, "[", 1); | |
| len += out.sink(out.data, mod, sizeof(mod)); | |
| len += write_digits(out, arg, 0, 0, 10, kDigitsLow); | |
| len += out.sink(out.data, "]", 1); | |
| } else { | |
| // Ok codes include only the arg | |
| len += write_digits(out, arg, 0, 0, 10, kDigitsLow); | |
| } | |
| len += out.sink(out.data, "}", as_json ? 1 : 0); | |
| return len; | |
| } | |
| /** | |
| * Hexdumps `bytes` onto `out`. | |
| * | |
| * @param out the sink to write bytes to. | |
| * @param bytes the bytes to dump. | |
| * @param len the number of bytes to dump. | |
| * @param width the minimum width to print; going below will result in writing | |
| * out zeroes. | |
| * @param padding the character to use for padding. | |
| * @param big_endian whether to print in big-endian order (i.e. as %x would). | |
| * @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 hex_dump(buffer_sink_t out, const char *bytes, size_t len, | |
| uint32_t width, char padding, bool big_endian, | |
| const char *glyphs) { | |
| size_t bytes_written = 0; | |
| char buf[32]; | |
| size_t buffered = 0; | |
| if (len < width) { | |
| width -= len; | |
| memset(buf, padding, sizeof(buf)); | |
| while (width > 0) { | |
| size_t to_write = width > ARRAYSIZE(buf) ? 32 : width; | |
| bytes_written += out.sink(out.data, buf, to_write); | |
| width -= to_write; | |
| } | |
| } | |
| for (size_t i = 0; i < len; ++i) { | |
| size_t idx = big_endian ? len - i - 1 : i; | |
| buf[buffered] = glyphs[(bytes[idx] >> 4) & 0xf]; | |
| buf[buffered + 1] = glyphs[bytes[idx] & 0xf]; | |
| buffered += 2; | |
| if (buffered == ARRAYSIZE(buf)) { | |
| bytes_written += out.sink(out.data, buf, buffered); | |
| buffered = 0; | |
| } | |
| } | |
| if (buffered != 0) { | |
| bytes_written += out.sink(out.data, buf, buffered); | |
| } | |
| return bytes_written; | |
| } | |
| /** | |
| * 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: { | |
| if (spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| *bytes_written += out.sink(out.data, "%", 1); | |
| break; | |
| } | |
| case kCharacter: { | |
| if (spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| char value = (char)va_arg(*args, uint32_t); | |
| *bytes_written += out.sink(out.data, &value, 1); | |
| break; | |
| } | |
| case kString: { | |
| size_t len = 0; | |
| if (spec.is_nonstd) { | |
| // This implements %!s. | |
| len = va_arg(*args, size_t); | |
| } | |
| char *value = va_arg(*args, char *); | |
| while (!spec.is_nonstd && value[len] != '\0') { | |
| // This implements %s. | |
| ++len; | |
| } | |
| *bytes_written += out.sink(out.data, value, len); | |
| break; | |
| } | |
| case kSignedDec1: | |
| case kSignedDec2: { | |
| if (spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| 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, spec.padding, 10, kDigitsLow); | |
| break; | |
| } | |
| case kUnsignedOct: { | |
| if (spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| uint32_t value = va_arg(*args, uint32_t); | |
| *bytes_written += | |
| write_digits(out, value, spec.width, spec.padding, 8, kDigitsLow); | |
| break; | |
| } | |
| case kPointer: { | |
| if (spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| // 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, '0', 16, kDigitsLow); | |
| break; | |
| } | |
| case kUnsignedHexLow: | |
| if (spec.is_nonstd) { | |
| size_t len = va_arg(*args, size_t); | |
| char *value = va_arg(*args, char *); | |
| *bytes_written += hex_dump(out, value, len, spec.width, spec.padding, | |
| /*big_endian=*/true, kDigitsLow); | |
| break; | |
| } | |
| OT_FALLTHROUGH_INTENDED; | |
| case kSvHexLow: { | |
| uint32_t value = va_arg(*args, uint32_t); | |
| *bytes_written += | |
| write_digits(out, value, spec.width, spec.padding, 16, kDigitsLow); | |
| break; | |
| } | |
| case kUnsignedHexHigh: | |
| if (spec.is_nonstd) { | |
| size_t len = va_arg(*args, size_t); | |
| char *value = va_arg(*args, char *); | |
| *bytes_written += hex_dump(out, value, len, spec.width, spec.padding, | |
| /*big_endian=*/true, kDigitsHigh); | |
| break; | |
| } | |
| OT_FALLTHROUGH_INTENDED; | |
| case kSvHexHigh: { | |
| uint32_t value = va_arg(*args, uint32_t); | |
| *bytes_written += | |
| write_digits(out, value, spec.width, spec.padding, 16, kDigitsHigh); | |
| break; | |
| } | |
| case kHexLeLow: { | |
| if (!spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| size_t len = va_arg(*args, size_t); | |
| char *value = va_arg(*args, char *); | |
| *bytes_written += hex_dump(out, value, len, spec.width, spec.padding, | |
| /*big_endian=*/false, kDigitsLow); | |
| break; | |
| } | |
| case kHexLeHigh: { | |
| if (!spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| size_t len = va_arg(*args, size_t); | |
| char *value = va_arg(*args, char *); | |
| *bytes_written += hex_dump(out, value, len, spec.width, spec.padding, | |
| /*big_endian=*/false, kDigitsHigh); | |
| break; | |
| } | |
| case kUnsignedDec: { | |
| if (spec.is_nonstd) { | |
| goto bad_spec; | |
| } | |
| uint32_t value = va_arg(*args, uint32_t); | |
| *bytes_written += | |
| write_digits(out, value, spec.width, spec.padding, 10, kDigitsLow); | |
| break; | |
| } | |
| case kSvBinary: { | |
| if (spec.is_nonstd) { | |
| // Bools passed into a ... function will be automatically promoted | |
| // to int; va_arg(..., bool) is actually UB! | |
| if (va_arg(*args, int) != 0) { | |
| *bytes_written += out.sink(out.data, "true", 4); | |
| } else { | |
| *bytes_written += out.sink(out.data, "false", 5); | |
| } | |
| break; | |
| } | |
| uint32_t value = va_arg(*args, uint32_t); | |
| *bytes_written += | |
| write_digits(out, value, spec.width, spec.padding, 2, kDigitsLow); | |
| break; | |
| } | |
| case kStatusResult: { | |
| status_t value = va_arg(*args, status_t); | |
| *bytes_written += write_status(out, value, spec.is_nonstd); | |
| break; | |
| } | |
| bad_spec: // Used with `goto` to bail out early. | |
| 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; | |
| } | |
| const char kBaseHexdumpDefaultFmtAlphabet[256] = | |
| // clang-format off | |
| // First 32 characters are not printable. | |
| "................................" | |
| // Printable ASCII. | |
| " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" | |
| // The rest of the range is also not printable (129 characters). | |
| "................................................................" | |
| "................................................................" | |
| "."; | |
| // clang-format on | |
| static const base_hexdump_fmt_t kBaseHexdumpDefaultFmt = { | |
| .bytes_per_word = 2, | |
| .words_per_line = 8, | |
| .alphabet = &kBaseHexdumpDefaultFmtAlphabet, | |
| }; | |
| size_t base_hexdump(const char *buf, size_t len) { | |
| return base_hexdump_with(kBaseHexdumpDefaultFmt, buf, len); | |
| } | |
| size_t base_snhexdump(char *out, size_t out_len, const char *buf, size_t len) { | |
| return base_snhexdump_with(out, out_len, kBaseHexdumpDefaultFmt, buf, len); | |
| } | |
| size_t base_fhexdump(buffer_sink_t out, const char *buf, size_t len) { | |
| return base_fhexdump_with(out, kBaseHexdumpDefaultFmt, buf, len); | |
| } | |
| size_t base_hexdump_with(base_hexdump_fmt_t fmt, const char *buf, size_t len) { | |
| return base_fhexdump_with(base_stdout, fmt, buf, len); | |
| } | |
| size_t base_snhexdump_with(char *out, size_t out_len, base_hexdump_fmt_t fmt, | |
| const char *buf, size_t len) { | |
| snprintf_captures_t data = { | |
| .buf = out, | |
| .bytes_left = out_len, | |
| }; | |
| buffer_sink_t sink = { | |
| .data = &data, | |
| .sink = &snprintf_sink, | |
| }; | |
| return base_fhexdump_with(sink, fmt, buf, len); | |
| } | |
| size_t base_fhexdump_with(buffer_sink_t out, base_hexdump_fmt_t fmt, | |
| const char *buf, size_t len) { | |
| size_t bytes_written = 0; | |
| size_t bytes_per_line = fmt.bytes_per_word * fmt.words_per_line; | |
| for (size_t line = 0; line < len; line += bytes_per_line) { | |
| bytes_written += base_fprintf(out, "%08x:", line); | |
| size_t chars_per_line = bytes_per_line * 2 + fmt.words_per_line; | |
| size_t line_bytes_written = 0; | |
| for (size_t word = 0; word < bytes_per_line; word += fmt.bytes_per_word) { | |
| if (len < line + word) { | |
| char spaces[16] = " "; | |
| while (line_bytes_written < chars_per_line) { | |
| size_t to_print = chars_per_line - line_bytes_written; | |
| if (to_print > sizeof(spaces)) { | |
| to_print = sizeof(spaces); | |
| } | |
| line_bytes_written += base_fprintf(out, "%!s", to_print, spaces); | |
| } | |
| break; | |
| } | |
| size_t bytes_left = len - line - word; | |
| if (bytes_left > fmt.bytes_per_word) { | |
| bytes_left = fmt.bytes_per_word; | |
| } | |
| line_bytes_written += base_fprintf(out, " "); | |
| line_bytes_written += | |
| hex_dump(out, buf + line + word, bytes_left, bytes_left, '0', | |
| /*big_endian=*/false, kDigitsLow); | |
| } | |
| bytes_written += line_bytes_written; | |
| bytes_written += base_fprintf(out, " "); | |
| size_t buffered = 0; | |
| char glyph_buffer[16]; | |
| for (size_t byte = 0; byte < bytes_per_line; ++byte) { | |
| if (buffered == sizeof(glyph_buffer)) { | |
| bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer); | |
| buffered = 0; | |
| } | |
| if (line + byte >= len) { | |
| break; | |
| } | |
| glyph_buffer[buffered++] = | |
| (*fmt.alphabet)[(size_t)(uint8_t)buf[line + byte]]; | |
| } | |
| if (buffered > 0) { | |
| bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer); | |
| } | |
| bytes_written += base_fprintf(out, "\n"); | |
| } | |
| return bytes_written; | |
| } |