[mask_rom] Add a UART driver to the mask ROM.

1. Write a very simple transmit-only uart driver.
2. Utilize the driver for printf in the mask ROM.
3. Create unit tests for the driver.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/device/silicon_creator/lib/drivers/meson.build b/sw/device/silicon_creator/lib/drivers/meson.build
new file mode 100644
index 0000000..eb839dd
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/meson.build
@@ -0,0 +1,34 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# Mask ROM uart driver
+sw_silicon_creator_lib_driver_uart = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_driver_uart',
+    sources: [
+      hw_ip_uart_reg_h,
+      'uart.c',
+    ],
+    dependencies: [
+      sw_lib_mmio,
+    ],
+  ),
+)
+
+test('sw_silicon_creator_lib_driver_uart_unittest', executable(
+  'sw_silicon_creator_lib_driver_uart_unittest',
+  sources: [
+    'uart_unittest.cc',
+    hw_ip_uart_reg_h,
+    'uart.c',
+  ],
+  dependencies: [
+    sw_vendor_gtest,
+    sw_lib_base_testing_mock_mmio,
+  ],
+  native: true,
+  c_args: ['-DMOCK_MMIO'],
+  cpp_args: ['-DMOCK_MMIO'],
+))
+
diff --git a/sw/device/silicon_creator/lib/drivers/uart.c b/sw/device/silicon_creator/lib/drivers/uart.c
new file mode 100644
index 0000000..8f6a466
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/uart.c
@@ -0,0 +1,111 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/uart.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/bitfield.h"
+#include "sw/device/lib/base/mmio.h"
+
+#include "uart_regs.h"  // Generated.
+
+#define NCO_WIDTH 16
+_Static_assert((1UL << NCO_WIDTH) - 1 == UART_CTRL_NCO_MASK,
+               "Bad value for NCO_WIDTH");
+
+static void uart_reset(const uart_t *uart) {
+  mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, 0u);
+
+  // Write to the relevant bits clears the FIFOs.
+  uint32_t reg = 0;
+  reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_RXRST_BIT, true);
+  reg = bitfield_bit32_write(reg, UART_FIFO_CTRL_TXRST_BIT, true);
+  mmio_region_write32(uart->base_addr, UART_FIFO_CTRL_REG_OFFSET, reg);
+
+  mmio_region_write32(uart->base_addr, UART_OVRD_REG_OFFSET, 0u);
+  mmio_region_write32(uart->base_addr, UART_TIMEOUT_CTRL_REG_OFFSET, 0u);
+  mmio_region_write32(uart->base_addr, UART_INTR_ENABLE_REG_OFFSET, 0u);
+  mmio_region_write32(uart->base_addr, UART_INTR_STATE_REG_OFFSET, UINT32_MAX);
+}
+
+int uart_init(const uart_t *uart) {
+  // TODO(#34): Change to use unified error space.
+  if (uart == NULL) {
+    return -1;
+  }
+
+  // TODO(#34): Change to use unified error space.
+  if (uart->baudrate == 0 || uart->clk_freq_hz == 0) {
+    return -1;
+  }
+
+  // Calculation formula: NCO = 16 * 2^nco_width * baud / fclk.
+  // NCO creates 16x of baudrate. So, in addition to the nco_width,
+  // 2^4 should be multiplied.
+  uint64_t nco =
+      ((uint64_t)uart->baudrate << (NCO_WIDTH + 4)) / uart->clk_freq_hz;
+  uint32_t nco_masked = nco & UART_CTRL_NCO_MASK;
+
+  // Requested baudrate is too high for the given clock frequency.
+  // TODO(#34): Change to use unified error space.
+  if (nco != nco_masked) {
+    return -1;
+  }
+
+  // Must be called before the first write to any of the UART registers.
+  uart_reset(uart);
+
+  // Set baudrate, TX, no parity bits.
+  uint32_t reg = 0;
+  reg = bitfield_field32_write(reg, UART_CTRL_NCO_FIELD, nco_masked);
+  reg = bitfield_bit32_write(reg, UART_CTRL_TX_BIT, true);
+  reg = bitfield_bit32_write(reg, UART_CTRL_PARITY_EN_BIT, false);
+  mmio_region_write32(uart->base_addr, UART_CTRL_REG_OFFSET, reg);
+
+  // Disable interrupts.
+  mmio_region_write32(uart->base_addr, UART_INTR_ENABLE_REG_OFFSET, 0u);
+  return 0;
+}
+
+static bool uart_tx_full(const uart_t *uart) {
+  uint32_t reg = mmio_region_read32(uart->base_addr, UART_STATUS_REG_OFFSET);
+  return bitfield_bit32_read(reg, UART_STATUS_TXFULL_BIT);
+}
+
+static bool uart_tx_idle(const uart_t *uart) {
+  uint32_t reg = mmio_region_read32(uart->base_addr, UART_STATUS_REG_OFFSET);
+  return bitfield_bit32_read(reg, UART_STATUS_TXIDLE_BIT);
+}
+
+void uart_putchar(const uart_t *uart, uint8_t byte) {
+  // If the transmit FIFO is full, wait.
+  while (uart_tx_full(uart)) {
+  }
+  uint32_t reg = bitfield_field32_write(0, UART_WDATA_WDATA_FIELD, byte);
+  mmio_region_write32(uart->base_addr, UART_WDATA_REG_OFFSET, reg);
+
+  // If the transmitter is active, wait.
+  while (!uart_tx_idle(uart)) {
+  }
+}
+
+/**
+ * Write `len` bytes to the UART TX FIFO.
+ */
+size_t uart_write(const uart_t *uart, const uint8_t *data, size_t len) {
+  size_t total = len;
+  while (len) {
+    uart_putchar(uart, *data);
+    data++;
+    len--;
+  }
+  return total;
+}
+
+size_t uart_sink(void *uart, const char *data, size_t len) {
+  return uart_write((const uart_t *)uart, (const uint8_t *)data, len);
+}
diff --git a/sw/device/silicon_creator/lib/drivers/uart.h b/sw/device/silicon_creator/lib/drivers/uart.h
new file mode 100644
index 0000000..4bd9dae
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/uart.h
@@ -0,0 +1,81 @@
+// 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_SILICON_CREATOR_LIB_DRIVERS_UART_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_UART_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "sw/device/lib/base/mmio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Initialization parameters for UART.
+ *
+ */
+typedef struct uart {
+  /**
+   * The base address for the UART hardware registers.
+   */
+  mmio_region_t base_addr;
+  /**
+   * The desired baudrate of the UART.
+   */
+  uint32_t baudrate;
+  /**
+   * The peripheral clock frequency (used to compute the UART baudrate divisor).
+   */
+  uint32_t clk_freq_hz;
+} uart_t;
+
+/**
+ * Initialize the UART with the request parameters.
+ *
+ * @param uart Pointer to uart_t with the requested parameters.
+ * @return 0 if successful, -1 otherwise (FIXME: unified error space).
+ */
+int uart_init(const uart_t *uart);
+
+/**
+ * Write a single byte to the UART.
+ *
+ * @param uart Pointer to uart_t represting the target UART.
+ * @param byte Byte to send.
+ */
+void uart_putchar(const uart_t *uart, uint8_t byte);
+
+/**
+ * Write a buffer to the UART.
+ *
+ * Writes the complete buffer to the UART and wait for transmision to complete.
+ *
+ * @param uart Pointer to uart_t represting the target UART.
+ * @param data Pointer to buffer to write.
+ * @param len Length of the buffer to write.
+ * @return Number of bytes written.
+ */
+size_t uart_write(const uart_t *uart, const uint8_t *data, size_t len);
+
+/**
+ * Sink a buffer to the UART.
+ *
+ * This is a wrapper for uart_write which conforms to the type signature
+ * required by the print library.
+ *
+ * @param uart Pointer to uart_t represting the target UART.
+ * @param data Pointer to buffer to write.
+ * @param len Length of the buffer to write.
+ * @return Number of bytes written.
+ */
+size_t uart_sink(void *uart, const char *data, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_DRIVERS_UART_H_
diff --git a/sw/device/silicon_creator/lib/drivers/uart_unittest.cc b/sw/device/silicon_creator/lib/drivers/uart_unittest.cc
new file mode 100644
index 0000000..5c0ddce
--- /dev/null
+++ b/sw/device/silicon_creator/lib/drivers/uart_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/drivers/uart.h"
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/base/testing/mock_mmio.h"
+
+#include "uart_regs.h"  // Generated.
+
+namespace uart_unittest {
+namespace {
+using mock_mmio::MmioTest;
+using mock_mmio::MockDevice;
+using testing::Each;
+using testing::Eq;
+using testing::Test;
+
+const std::vector<uint8_t> kBytesArray = {
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
+    'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+    'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+};
+
+class UartTest : public Test, public MmioTest {
+ protected:
+  void ExpectDeviceReset() {
+    EXPECT_WRITE32(UART_CTRL_REG_OFFSET, 0);
+    EXPECT_WRITE32(UART_FIFO_CTRL_REG_OFFSET,
+                   {
+                       {UART_FIFO_CTRL_RXRST_BIT, true},
+                       {UART_FIFO_CTRL_TXRST_BIT, true},
+                   });
+    EXPECT_WRITE32(UART_OVRD_REG_OFFSET, 0);
+    EXPECT_WRITE32(UART_TIMEOUT_CTRL_REG_OFFSET, 0);
+    EXPECT_WRITE32(UART_INTR_ENABLE_REG_OFFSET, 0);
+    EXPECT_WRITE32(UART_INTR_STATE_REG_OFFSET,
+                   std::numeric_limits<uint32_t>::max());
+  }
+
+  uart_t uart_ = {
+      .base_addr = dev().region(),
+      .baudrate = 1,
+      .clk_freq_hz = 1048576,
+  };
+};
+
+class InitTest : public UartTest {};
+
+TEST_F(InitTest, NullArgs) {
+  // FIXME: unified error space.
+  // FIXME: add tests with `uart_` misconfigured.
+  EXPECT_EQ(uart_init(nullptr), -1);
+}
+
+TEST_F(InitTest, Initialize) {
+  ExpectDeviceReset();
+
+  EXPECT_WRITE32(UART_CTRL_REG_OFFSET, {
+                                           {UART_CTRL_TX_BIT, true},
+                                           {UART_CTRL_NCO_OFFSET, 1},
+                                       });
+  EXPECT_WRITE32(UART_INTR_ENABLE_REG_OFFSET, 0);
+
+  EXPECT_EQ(uart_init(&uart_), 0);
+}
+
+class BytesSendTest : public UartTest {
+ protected:
+  /**
+   * Sets TX bytes expectations.
+   *
+   * Every sent byte by the "send bytes" routine is expected to result in the
+   * STATUS read of 0 (FIFO not full), and write to WDATA.
+   */
+  void ExpectSendBytes(int num_elements = kBytesArray.size()) {
+    ASSERT_LE(num_elements, kBytesArray.size());
+    for (int i = 0; i < num_elements; ++i) {
+      uint32_t value = static_cast<uint32_t>(kBytesArray[i]);
+      EXPECT_READ32(UART_STATUS_REG_OFFSET, {{UART_STATUS_TXFULL_BIT, false}});
+      EXPECT_WRITE32(UART_WDATA_REG_OFFSET, value);
+      EXPECT_READ32(UART_STATUS_REG_OFFSET, {{UART_STATUS_TXIDLE_BIT, true}});
+    }
+  }
+};
+
+TEST_F(BytesSendTest, SendBuffer) {
+  ExpectSendBytes();
+  // Calling uart_write implicitly tests uart_putchar.
+  EXPECT_EQ(uart_write(&uart_, kBytesArray.data(), kBytesArray.size()),
+            kBytesArray.size());
+}
+
+TEST_F(BytesSendTest, SendByteBusy) {
+  // FIFO full for one cycle, then empty.
+  EXPECT_READ32(UART_STATUS_REG_OFFSET, {{UART_STATUS_TXFULL_BIT, true}});
+  EXPECT_READ32(UART_STATUS_REG_OFFSET, {{UART_STATUS_TXFULL_BIT, false}});
+
+  // The value sent is 'X'
+  EXPECT_WRITE32(UART_WDATA_REG_OFFSET, 'X');
+
+  // Transmitter busy for one cycle, then idle.
+  EXPECT_READ32(UART_STATUS_REG_OFFSET, {{UART_STATUS_TXIDLE_BIT, false}});
+  EXPECT_READ32(UART_STATUS_REG_OFFSET, {{UART_STATUS_TXIDLE_BIT, true}});
+
+  uart_putchar(&uart_, 'X');
+}
+
+}  // namespace
+}  // namespace uart_unittest
diff --git a/sw/device/silicon_creator/lib/fake_deps.c b/sw/device/silicon_creator/lib/fake_deps.c
new file mode 100644
index 0000000..6ee2990
--- /dev/null
+++ b/sw/device/silicon_creator/lib/fake_deps.c
@@ -0,0 +1,10 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <stdint.h>
+
+// FIXME: this symbol is needed by runtime/print.c.  Delete this once
+// we eliminate this dependency from print.c or create a local copy for
+// ROM use.
+int dif_uart_byte_send_polled(const void *uart, uint8_t data) { return 0; }
diff --git a/sw/device/silicon_creator/lib/meson.build b/sw/device/silicon_creator/lib/meson.build
new file mode 100644
index 0000000..be3b034
--- /dev/null
+++ b/sw/device/silicon_creator/lib/meson.build
@@ -0,0 +1,17 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+subdir('drivers')
+
+# Mask ROM fake dependencies.  These are temporary dependencies until we
+# refactor or copy the other libraries we need (e.g. `runtime`).
+sw_silicon_creator_lib_fake_deps = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_fake_deps',
+    sources: [
+      'fake_deps.c',
+    ],
+    dependencies: [
+    ],
+  ),
+)
diff --git a/sw/device/silicon_creator/mask_rom/mask_rom.c b/sw/device/silicon_creator/mask_rom/mask_rom.c
index 70a0c99..4381b31 100644
--- a/sw/device/silicon_creator/mask_rom/mask_rom.c
+++ b/sw/device/silicon_creator/mask_rom/mask_rom.c
@@ -7,10 +7,13 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+#include "sw/device/lib/arch/device.h"
 #include "sw/device/lib/base/csr.h"
 #include "sw/device/lib/base/stdasm.h"
 #include "sw/device/lib/pinmux.h"
 #include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/print.h"
+#include "sw/device/silicon_creator/lib/drivers/uart.h"
 #include "sw/device/silicon_creator/rom_exts/rom_ext_manifest_parser.h"
 
 #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
@@ -23,8 +26,25 @@
 void mask_rom_exception_handler(void) { wait_for_interrupt(); }
 void mask_rom_nmi_handler(void) { wait_for_interrupt(); }
 
+uart_t uart;
+
 void mask_rom_boot(void) {
+  // Initialize pinmux configuration so we can use the UART.
   pinmux_init();
+
+  // Configure UART0 as stdout.
+  uart.base_addr = mmio_region_from_addr(TOP_EARLGREY_UART0_BASE_ADDR);
+  uart.baudrate = kUartBaudrate;
+  uart.clk_freq_hz = kClockFreqPeripheralHz;
+  uart_init(&uart);
+  base_set_stdout((buffer_sink_t){
+      .data = &uart,
+      .sink = uart_sink,
+  });
+
+  // FIXME: what (if anything) should we print at startup?
+  base_printf("MaskROM\r\n");
+
   // boot_reason = read_boot_reason(); // Boot Policy Module
 
   // Clean Device State Part 2.
@@ -160,6 +180,7 @@
 
       // Jump to ROM_EXT entry point.
       boot_fn *rom_ext_entry = (boot_fn *)rom_ext_get_entry(rom_ext);
+      base_printf("rom_ext_entry: %p\r\n", rom_ext_entry);
       rom_ext_entry();
       // NOTE: never expecting a return, but if something were to go wrong
       // in the real `jump` implementation, we need to enter a failure case.
diff --git a/sw/device/silicon_creator/mask_rom/meson.build b/sw/device/silicon_creator/mask_rom/meson.build
index 9483add..8f98e4d 100644
--- a/sw/device/silicon_creator/mask_rom/meson.build
+++ b/sw/device/silicon_creator/mask_rom/meson.build
@@ -23,9 +23,12 @@
     link_args: rom_link_args,
     dependencies: [
       freestanding_headers,
+      sw_silicon_creator_lib_driver_uart,
+      sw_silicon_creator_lib_fake_deps,
       rom_ext_manifest_parser,
       sw_lib_crt,
       sw_lib_pinmux,
+      sw_lib_runtime_print,
     ],
     link_with: static_library(
       'mask_rom_lib',
diff --git a/sw/device/silicon_creator/meson.build b/sw/device/silicon_creator/meson.build
index d04ba30..3e1ad49 100644
--- a/sw/device/silicon_creator/meson.build
+++ b/sw/device/silicon_creator/meson.build
@@ -2,5 +2,6 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
+subdir('lib')
 subdir('rom_exts')
 subdir('mask_rom')