[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')