[sw] Move printf-like facilities into libbase.
|printf|-like functions simply move bytes around in a fancy way, and
having them accessible everywhere is probably useful.
As #1162 is addressed, we will see if we can use some link-time
mechanism to more effectively control the "default" sink used by
|base_printf()|.
Signed-off-by: Miguel Young de la Sota <mcyoung@google.com>
diff --git a/sw/device/lib/base/meson.build b/sw/device/lib/base/meson.build
index 7776626..a69ebcf 100644
--- a/sw/device/lib/base/meson.build
+++ b/sw/device/lib/base/meson.build
@@ -20,3 +20,21 @@
sources: ['mmio.c'],
)
)
+
+# Basic printing library (sw_lib_base_print)
+sw_lib_base_print = declare_dependency(
+ link_with: static_library(
+ 'base_print_ot',
+ sources: ['print.c'],
+ )
+)
+
+test('sw_lib_base_print_test', executable(
+ 'sw_lib_base_print_test',
+ sources: ['print.c', 'print_test.cc'],
+ dependencies: [
+ sw_vendor_gtest,
+ ],
+ native: true,
+))
+
diff --git a/sw/device/lib/base/print.c b/sw/device/lib/base/print.c
new file mode 100644
index 0000000..5390003
--- /dev/null
+++ b/sw/device/lib/base/print.c
@@ -0,0 +1,345 @@
+// 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/base/print.h"
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.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;
+}
+
+size_t base_printf(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ size_t bytes_left = base_vfprintf(base_stdout, format, args);
+ va_end(args);
+ return bytes_left;
+}
+
+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;
+ }
+ __builtin_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 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 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 bytes_written out param for the number of bytes writen to |out|.
+ * @param va_list 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;
+ }
+
+ 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);
+ }
+
+ return bytes_written;
+}
diff --git a/sw/device/lib/base/print.h b/sw/device/lib/base/print.h
new file mode 100644
index 0000000..7b81cd2
--- /dev/null
+++ b/sw/device/lib/base/print.h
@@ -0,0 +1,151 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_BASE_PRINT_H_
+#define OPENTITAN_SW_DEVICE_LIB_BASE_PRINT_H_
+
+#include <stdarg.h>
+#include <stddef.h>
+
+/**
+ * This header provides libc-like printing facilities, which is agnostic of the
+ * underlying hardware printing mechanism.
+ *
+ * We avoid using libc names here, since we do not support the full suite of
+ * format specifier syntax, and use a different character sink type instead of
+ * the traditional |FILE *|.
+ *
+ * All functions in this file should be machine word size agnostic, that is, the
+ * same code should work correctly on both 32-bit and 64-bit machines, though
+ * formatting, where the exact format style is unspecified, is allowed to vary
+ * slightly on machine word size.
+ */
+
+/**
+ * A buffer_sink_t represents a place to write bytes to, implemented as a
+ * C-style "closure".
+ *
+ * It consists of a generic data pointer, which can hold instance-specific
+ * information, and a sink function, which takes the data pointer, a buffer, and
+ * that buffer's length.
+ *
+ * The sink function should return the number of bytes actually written.
+ */
+typedef struct buffer_sink {
+ void *data;
+ size_t (*sink)(void *data, const char *buf, size_t len);
+} buffer_sink_t;
+
+/**
+ * Prints out a message to stdout, formatted according to the format string
+ * |format|.
+ *
+ * The definition of "stdout" is not provided by this library; rather, it must
+ * be initialized using |base_set_stdout()|.
+ *
+ * This function supports a subset of the format specifiers provided by standard
+ * C |printf|. Those are, namely:
+ * - %%, which prints a percent sign.
+ * - %c, which prints the lowest byte of a uint32_t as a character.
+ * - %s, which prints a NUL-terminated string.
+ * - %d and %i, which print a signed decimal uint32_t.
+ * - %u, which prints an unsigned decimal uint32_t.
+ * - %o, which prints an unsigned octal uint32_t.
+ * - %x and %X, which print an unsigned hex uint32_t.
+ * - %p, which prints a pointer in a consistent but unspecified way.
+ *
+ * Additionally, three SystemVerilog format specifiers are supported:
+ * - %h and %H, which are aliases for %x and %X, respectively.
+ * - %b, which prints an unsigned binary uint32_t.
+ *
+ * Finally, an additional nonstandard format specifier is supported:
+ * - %z, which takes a size_t followed by a pointer to a buffer, and prints
+ * out that many characters from the buffer.
+ *
+ * When compiled for a DV testbench, this function will not read any pointers,
+ * and as such the specifiers %s and %z will behave as if they were printing
+ * garbage, and are, as such, unsupported.
+ *
+ * This function furthermore supports width modifiers for integer specifiers,
+ * such as |%10d|. It does not support dynamic widths like |%*d|, and will also
+ * always pad with zeroes, rather than spaces.
+ *
+ * Of course, providing arguments for formatting which are incompatible with a
+ * given format specifier is Undefined Behavior.
+ *
+ * @param format the format spec.
+ * @param ... values to interpolate in the format spec.
+ */
+size_t base_printf(const char *format, ...);
+
+/*
+ * Prints a message to the buffer |buf|, capped at a given length.
+ *
+ * It goes without saying that the caller must ensure the given buffer is large
+ * enough; failure to do so is Undefined Behavior.
+ *
+ * See |base_printf()| for the semantics of the format specification.
+ *
+ * @param buf a buffer to print to.
+ * @param format the format spec.
+ * @param ... values to interpolate in the format spec.
+ */
+size_t base_snprintf(char *buf, size_t len, const char *format, ...);
+
+/**
+ * Prints a message to the sink |out|.
+ *
+ * If |out.sink| is |NULL|, writes are treated as-if they were written to a
+ * UNIX-like /dev/null: writes succeed, but the actual bytes are not printed
+ * anywhere.
+ *
+ * See |base_printf()| for the semantics of the format specification.
+ *
+ * @param out a sink to print to.
+ * @param format the format spec.
+ * @param ... values to interpolate in the format spec.
+ */
+size_t base_fprintf(buffer_sink_t out, const char *format, ...);
+
+/**
+ * Prints a message to the sink |out|.
+ *
+ * This function is identical to |base_fprintf|, except in that it takes a
+ * |va_list| instead of having a vararg parameter. This function is provided
+ * not for calling directly, but rather for being called by functions that
+ * already take a variable number of arguments, and wish to make use of
+ * formatting facilities.
+ *
+ * This function *does not* take ownership of |args|; callers are responsible
+ * for calling |va_end|.
+ *
+ * If |out.sink| is |NULL|, writes are treated as-if they were written to a
+ * UNIX-like /dev/null: writes succeed, but the actual bytes are not printed
+ * anywhere.
+ *
+ * See |base_printf()| for the semantics of the format specification.
+ *
+ * @param out a sink to print to.
+ * @param format the format spec.
+ * @param args values to interpolate in the format spec.
+ */
+size_t base_vfprintf(buffer_sink_t out, const char *format, va_list args);
+
+/**
+ * Sets what the "stdout" sink is, which is used by |base_printf()|.
+ *
+ * The default sink behaves like /dev/null on a standard UNIX system: writes
+ * are treated as successful, but the contents of buffers are ignored.
+ *
+ * As such, this function must be called for printed messages to wind up
+ * somewhere.
+ *
+ * Passing in |NULL| instead of a real function pointer will reset stdout to
+ * the default /dev/null behavior.
+ *
+ * @param out the sink to use for "default" printing.
+ */
+void base_set_stdout(buffer_sink_t out);
+
+#endif // OPENTITAN_SW_DEVICE_LIB_BASE_PRINT_H_
diff --git a/sw/device/lib/base/print_test.cc b/sw/device/lib/base/print_test.cc
new file mode 100644
index 0000000..3399735
--- /dev/null
+++ b/sw/device/lib/base/print_test.cc
@@ -0,0 +1,241 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+extern "C" {
+#include "sw/device/lib/base/print.h"
+} // extern "C"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace base {
+namespace {
+
+using ::testing::StartsWith;
+
+// A test fixture for automatiocally capturing stdout.
+class PrintfTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ base_set_stdout({/*data=*/static_cast<void *>(&buf_),
+ /*sink=*/+[](void *data, const char *buf, size_t len) {
+ static_cast<std::string *>(data)->append(buf, len);
+ return len;
+ }});
+ }
+
+ std::string buf_;
+};
+
+TEST_F(PrintfTest, EmptyFormat) {
+ EXPECT_EQ(base_printf(""), 0);
+ EXPECT_EQ(buf_, "");
+}
+
+TEST_F(PrintfTest, TrivialText) {
+ EXPECT_EQ(base_printf("Hello, World!\n"), 14);
+ EXPECT_EQ(buf_, "Hello, World!\n");
+}
+
+TEST_F(PrintfTest, PartialPrints) {
+ EXPECT_EQ(base_printf("Hello, "), 7);
+ EXPECT_EQ(buf_, "Hello, ");
+ EXPECT_EQ(base_printf("World!\n"), 7);
+ EXPECT_EQ(buf_, "Hello, World!\n");
+}
+
+TEST_F(PrintfTest, LiteralPct) {
+ EXPECT_EQ(base_printf("Hello, %%!\n"), 10);
+ EXPECT_EQ(buf_, "Hello, %!\n");
+}
+
+TEST_F(PrintfTest, Character) {
+ EXPECT_EQ(base_printf("Hello, %c!\n", 'X'), 10);
+ EXPECT_EQ(buf_, "Hello, X!\n");
+}
+
+TEST_F(PrintfTest, StringWithNul) {
+ EXPECT_EQ(base_printf("Hello, %s!\n", "abcxyz"), 15);
+ EXPECT_EQ(buf_, "Hello, abcxyz!\n");
+}
+
+TEST_F(PrintfTest, StringWithLen) {
+ EXPECT_EQ(base_printf("Hello, %z!\n", 6, "abcxyz"), 15);
+ EXPECT_EQ(buf_, "Hello, abcxyz!\n");
+}
+
+TEST_F(PrintfTest, StringWithLenPrefix) {
+ EXPECT_EQ(base_printf("Hello, %z!\n", 3, "abcxyz"), 12);
+ EXPECT_EQ(buf_, "Hello, abc!\n");
+}
+
+TEST_F(PrintfTest, StringWithLenZeroLen) {
+ EXPECT_EQ(base_printf("Hello, %z!\n", 0, "abcxyz"), 9);
+ EXPECT_EQ(buf_, "Hello, !\n");
+}
+
+TEST_F(PrintfTest, SignedInt) {
+ EXPECT_EQ(base_printf("Hello, %i!\n", 42), 11);
+ EXPECT_EQ(buf_, "Hello, 42!\n");
+}
+
+TEST_F(PrintfTest, SignedIntAlt) {
+ EXPECT_EQ(base_printf("Hello, %d!\n", 42), 11);
+ EXPECT_EQ(buf_, "Hello, 42!\n");
+}
+
+TEST_F(PrintfTest, SignedIntNegative) {
+ EXPECT_EQ(base_printf("Hello, %i!\n", -800), 13);
+ EXPECT_EQ(buf_, "Hello, -800!\n");
+}
+
+TEST_F(PrintfTest, SignedIntWithWidth) {
+ EXPECT_EQ(base_printf("Hello, %3i!\n", 42), 12);
+ EXPECT_EQ(buf_, "Hello, 042!\n");
+}
+
+TEST_F(PrintfTest, SignedIntWithWidthTooShort) {
+ EXPECT_EQ(base_printf("Hello, %3i!\n", 9001), 13);
+ EXPECT_EQ(buf_, "Hello, 9001!\n");
+}
+
+TEST_F(PrintfTest, UnsignedInt) {
+ EXPECT_EQ(base_printf("Hello, %u!\n", 42), 11);
+ EXPECT_EQ(buf_, "Hello, 42!\n");
+}
+
+TEST_F(PrintfTest, UnsignedIntNegative) {
+ EXPECT_EQ(base_printf("Hello, %u!\n", -1), 19);
+ EXPECT_EQ(buf_, "Hello, 4294967295!\n");
+}
+
+TEST_F(PrintfTest, HexFromDec) {
+ EXPECT_EQ(base_printf("Hello, %x!\n", 1024), 12);
+ EXPECT_EQ(buf_, "Hello, 400!\n");
+}
+
+TEST_F(PrintfTest, HexFromDecWithWidth) {
+ EXPECT_EQ(base_printf("Hello, %8x!\n", 1024), 17);
+ EXPECT_EQ(buf_, "Hello, 00000400!\n");
+}
+
+TEST_F(PrintfTest, HexLower) {
+ EXPECT_EQ(base_printf("Hello, %x!\n", 0xdead'beef), 17);
+ EXPECT_EQ(buf_, "Hello, deadbeef!\n");
+}
+
+TEST_F(PrintfTest, HexUpper) {
+ EXPECT_EQ(base_printf("Hello, %X!\n", 0xdead'beef), 17);
+ EXPECT_EQ(buf_, "Hello, DEADBEEF!\n");
+}
+
+TEST_F(PrintfTest, HexNegative) {
+ EXPECT_EQ(base_printf("Hello, %x!\n", -1), 17);
+ EXPECT_EQ(buf_, "Hello, ffffffff!\n");
+}
+
+TEST_F(PrintfTest, HexSvLower) {
+ EXPECT_EQ(base_printf("Hello, %h!\n", 0xdead'beef), 17);
+ EXPECT_EQ(buf_, "Hello, deadbeef!\n");
+}
+
+TEST_F(PrintfTest, HexSvUpper) {
+ EXPECT_EQ(base_printf("Hello, %H!\n", 0xdead'beef), 17);
+ EXPECT_EQ(buf_, "Hello, DEADBEEF!\n");
+}
+
+TEST_F(PrintfTest, Pointer) {
+ auto *ptr = reinterpret_cast<uint32_t *>(0x1234);
+ base_printf("Hello, %p!\n", ptr);
+ switch (sizeof(uintptr_t)) {
+ case 4:
+ EXPECT_EQ(buf_, "Hello, 0x00001234!\n");
+ break;
+ case 8:
+ EXPECT_EQ(buf_, "Hello, 0x0000000000001234!\n");
+ break;
+ }
+}
+
+TEST_F(PrintfTest, NullPtr) {
+ base_printf("Hello, %p!\n", nullptr);
+ switch (sizeof(uintptr_t)) {
+ case 4:
+ EXPECT_EQ(buf_, "Hello, 0x00000000!\n");
+ break;
+ case 8:
+ EXPECT_EQ(buf_, "Hello, 0x0000000000000000!\n");
+ break;
+ }
+}
+
+TEST_F(PrintfTest, Octal) {
+ EXPECT_EQ(base_printf("Hello, %o!\n", 01234567), 16);
+ EXPECT_EQ(buf_, "Hello, 1234567!\n");
+}
+
+TEST_F(PrintfTest, Binary) {
+ EXPECT_EQ(base_printf("Hello, %b!\n", 0b1010'1010), 17);
+ EXPECT_EQ(buf_, "Hello, 10101010!\n");
+}
+
+TEST_F(PrintfTest, BinaryWithWidth) {
+ EXPECT_EQ(base_printf("Hello, %32b!\n", 0b1010'1010), 41);
+ EXPECT_EQ(buf_, "Hello, 00000000000000000000000010101010!\n");
+}
+
+TEST_F(PrintfTest, IncompleteSpec) {
+ base_printf("Hello, %");
+ EXPECT_THAT(buf_, StartsWith("Hello, "));
+}
+
+TEST_F(PrintfTest, UnknownSpec) {
+ base_printf("Hello, %j");
+ EXPECT_THAT(buf_, StartsWith("Hello, "));
+}
+
+TEST_F(PrintfTest, WidthTooNarrow) {
+ base_printf("Hello, %0x");
+ EXPECT_THAT(buf_, StartsWith("Hello, "));
+}
+
+TEST_F(PrintfTest, WidthTooWide) {
+ base_printf("Hello, %9001x");
+ EXPECT_THAT(buf_, StartsWith("Hello, "));
+}
+
+TEST_F(PrintfTest, ManySpecifiers) {
+ base_printf("%d + %d == %d, also spelled 0x%x", 2, 8, 2 + 8, 2 + 8);
+ EXPECT_THAT(buf_, StartsWith("2 + 8 == 10, also spelled 0xa"));
+}
+
+TEST(SnprintfTest, SimpleWrite) {
+ std::string buf(128, '\0');
+ auto len = base_snprintf(&buf[0], buf.size(), "Hello, World!\n");
+ buf.resize(len);
+ EXPECT_EQ(len, 14);
+ EXPECT_EQ(buf, "Hello, World!\n");
+}
+
+TEST(SnprintfTest, ComplexFormating) {
+ std::string buf(128, '\0');
+ auto len = base_snprintf(&buf[0], buf.size(), "%d + %d == %d, also spelled 0x%x", 2, 8, 2 + 8, 2 + 8);
+ buf.resize(len);
+ EXPECT_EQ(buf, "2 + 8 == 10, also spelled 0xa");
+}
+
+TEST(SnprintfTest, PartialWrite) {
+ std::string buf(16, '\0');
+ auto len = base_snprintf(&buf[0], buf.size(), "%d + %d == %d, also spelled 0x%x", 2, 8, 2 + 8, 2 + 8);
+ buf.resize(len);
+ EXPECT_EQ(len, 16);
+ EXPECT_EQ(buf, "2 + 8 == 10, als");
+}
+
+} // namespace
+} // namespace base
diff --git a/sw/device/lib/log_uart/log_impl.h b/sw/device/lib/log_uart/log_impl.h
index f43f4ca..d7eb2c9 100644
--- a/sw/device/lib/log_uart/log_impl.h
+++ b/sw/device/lib/log_uart/log_impl.h
@@ -5,7 +5,7 @@
#ifndef OPENTITAN_SW_DEVICE_LIB_LOG_UART_LOG_IMPL_H_
#define OPENTITAN_SW_DEVICE_LIB_LOG_UART_LOG_IMPL_H_
-#include "sw/device/lib/print_log.h"
+#include "sw/device/lib/base/print.h"
#include "sw/device/lib/uart.h"
// Stringify stuff.
@@ -52,6 +52,6 @@
* this macro underneath) instead.
*/
#define PRINT_LOG(log_header, ...) \
- print_log(&uart_send_char, log_header __VA_ARGS__);
+ base_fprintf(uart_stdout, log_header __VA_ARGS__);
#endif // OPENTITAN_SW_DEVICE_LIB_LOG_UART_LOG_IMPL_H_
diff --git a/sw/device/lib/meson.build b/sw/device/lib/meson.build
index cacb167..839f1a7 100644
--- a/sw/device/lib/meson.build
+++ b/sw/device/lib/meson.build
@@ -14,6 +14,7 @@
'uart_ot',
sources: ['uart.c'],
dependencies: [
+ sw_lib_base_print,
sw_lib_runtime_ibex,
dif_uart,
],
@@ -148,16 +149,3 @@
]
)
)
-
-# Logging library that prints to UART directly (sw_lib_log)
-sw_lib_log = declare_dependency(
- link_with: static_library(
- 'log_ot',
- sources: [
- 'print_log.c',
- ],
- dependencies: [
- sw_lib_uart,
- ]
- )
-)
diff --git a/sw/device/lib/print_log.c b/sw/device/lib/print_log.c
deleted file mode 100644
index 302ed6f..0000000
--- a/sw/device/lib/print_log.c
+++ /dev/null
@@ -1,118 +0,0 @@
-// 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/print_log.h"
-
-#include <stdarg.h>
-
-// Identifiers for string format type specifiers.
-static const char kFormatSpecifier = '%';
-static const char kBinary = 'b'; // Print binary [SystemVerilog style].
-static const char kDecimalI = 'i'; // Signed decimal.
-static const char kDecimal = 'd'; // Signed decimal.
-static const char kUnsigned = 'u'; // Unsigned decimal.
-static const char kOctal = 'o'; // Octal.
-static const char kAsciiChar = 'c'; // Single character byte.
-static const char kHexUpper = 'X'; // Upper case hex.
-static const char kHexAltUpper = 'H'; // Upper case hex [SystemVerilog style].
-static const char kHexLower = 'x'; // Lower case hex.
-static const char kHexAltLower = 'h'; // Lower case hex [SystemVerilog style].
-static const char kPercent = '%'; // Escape '%'.
-
-static inline void print_digit(print_char_func print_char, unsigned int digit) {
- print_char("0123456789ABCDEF"[digit]);
-}
-
-static inline void print_num(print_char_func print_char, int width,
- unsigned int n, unsigned int base) {
- // TODO: Consider changing this to for loop.
- if (--width > 0 || n >= base) {
- print_num(print_char, width, n / base, base);
- }
- print_digit(print_char, n % base);
-}
-
-void print_log(print_char_func print_char, const char *fmt, ...) {
- va_list va;
- va_start(va, fmt);
-
- while (*fmt != '\0') {
- char ch = *fmt++;
- if (ch != kFormatSpecifier) {
- // Add CR to new line automatically (if not added already).
- if (ch == '\n' && !(*(fmt - 1) == '\r' || *(fmt + 1) == '\r')) {
- print_char('\r');
- }
- print_char(ch);
- } else {
- int w = 0;
- // TODO: Refactor this into a separate function.
- while (*fmt != '\0') {
- ch = *fmt++;
- // Parse width field.
- if (ch >= '0' && ch <= '9') {
- w = w * 10 + (ch - '0');
- continue;
- } else {
- switch (ch) {
- case '\0': {
- return;
- }
- case kBinary: {
- unsigned int n = va_arg(va, unsigned int);
- print_num(print_char, w, n, 2);
- break;
- }
- case kDecimalI:
- case kDecimal: {
- unsigned int n = va_arg(va, unsigned int);
- if (((int)n) < 0) {
- print_char('-');
- n = -n;
- }
- print_num(print_char, w, n, 10);
- break;
- }
- case kUnsigned: {
- unsigned int n = va_arg(va, unsigned int);
- print_num(print_char, w, n, 10);
- break;
- }
- case kOctal: {
- unsigned int n = va_arg(va, unsigned int);
- print_num(print_char, w, n, 8);
- break;
- }
- case kHexLower:
- case kHexAltLower:
- case kHexUpper:
- case kHexAltUpper: {
- // TODO: This will still print in upper case.
- unsigned int n = va_arg(va, unsigned int);
- print_num(print_char, w, n, 16);
- break;
- }
- case kAsciiChar: {
- char c = va_arg(va, int);
- print_char(c);
- break;
- }
- case kPercent: {
- print_char('%');
- break;
- }
- default: {
- // Unknown format - this error message is printed inline within
- // the message.
- print_log(print_char, "[INVALID SPECIFIER: %%%c]", ch);
- break;
- }
- }
- break;
- }
- }
- }
- }
- va_end(va);
-}
diff --git a/sw/device/lib/print_log.h b/sw/device/lib/print_log.h
deleted file mode 100644
index 4e1c6bd..0000000
--- a/sw/device/lib/print_log.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-#ifndef OPENTITAN_SW_DEVICE_LIB_PRINT_LOG_H_
-#define OPENTITAN_SW_DEVICE_LIB_PRINT_LOG_H_
-
-// Pointer to a function that prints a character.
-typedef void (*print_char_func)(char);
-
-/**
- * Generic print log function that prints a format string with variable
- * number of type specifiers and arguments through a real hardware in the chip
- *
- * Function uses a format string as input which can contain a variable number
- * of type specifiers. These are fulfilled with with variable number of
- * corresponding arguments. It also takes a function pointer (which itself
- * takes a single char argument as input) as an argument to print (write) the
- * whole message string char by char through the HW IOs.
- *
- * To ensure portability of code across different platforms (DV simulations,
- * FPGA based emulations with production SW, simulations using Verilator,
- * etc.), DO NOT CALL THIS FUNCTION DIRECTLY! Instead please use the generic
- * logging APIs defined in msg.h.
- * Also, the list of supported format specifiers is limited to integer types
- * (%c, %d, %x, %X).
- *
- * @param print_char: Function pointer that takes single character as input and
- * writes it to a HW in the chip such as UART for printing.
- * @param fmt: Format string message with type specifiers. To maintain
- * compatibility with the logging API implementation for DV, the
- * type specifiers are limited to integer types.
- * @param ...: Arguments passed to the format string based on the type
- * specifiers in fmt.
- */
-void print_log(print_char_func print_char, const char *fmt, ...);
-
-#endif // OPENTITAN_SW_DEVICE_LIB_PRINT_LOG_H_
diff --git a/sw/device/lib/uart.c b/sw/device/lib/uart.c
index 5ed7da7..9190bb2 100644
--- a/sw/device/lib/uart.c
+++ b/sw/device/lib/uart.c
@@ -34,6 +34,18 @@
}
}
+size_t uart_send_buf(void *data, const char *buf, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ uart_send_char(buf[i]);
+ }
+ return len;
+}
+
+const buffer_sink_t uart_stdout = {
+ .data = NULL,
+ .sink = &uart_send_buf,
+};
+
#define hexchar(i) (((i & 0xf) > 9) ? (i & 0xf) - 10 + 'A' : (i & 0xf) + '0')
void uart_send_uint(uint32_t n, int bits) {
diff --git a/sw/device/lib/uart.h b/sw/device/lib/uart.h
index 7a6fe3f..c35ab9b 100644
--- a/sw/device/lib/uart.h
+++ b/sw/device/lib/uart.h
@@ -5,8 +5,11 @@
#ifndef OPENTITAN_SW_DEVICE_LIB_UART_H_
#define OPENTITAN_SW_DEVICE_LIB_UART_H_
+#include <stddef.h>
#include <stdint.h>
+#include "sw/device/lib/base/print.h"
+
void uart_send_char(char c);
/**
@@ -20,6 +23,8 @@
*/
void uart_send_str(char *str);
+extern const buffer_sink_t uart_stdout;
+
/**
* Receive a single character from UART
*