[otbn/sw] Add OTBN device driver

Add a basic device driver for OTBN which is based on the DIF.

This driver is neither complete nor fully designed for all possible use
cases. It is a starting point where we can place reusable code which is
used in the tests we're writing for OTBN code.

Signed-off-by: Philipp Wagner <phw@lowrisc.org>
diff --git a/sw/device/lib/runtime/meson.build b/sw/device/lib/runtime/meson.build
index 179cb91..b82e001 100644
--- a/sw/device/lib/runtime/meson.build
+++ b/sw/device/lib/runtime/meson.build
@@ -50,3 +50,17 @@
     ],
   )
 )
+
+sw_lib_runtime_otbn = declare_dependency(
+  link_with: static_library(
+    'otbn_ot',
+    sources: [
+      'otbn.c',
+    ],
+    dependencies: [
+      sw_lib_dif_otbn,
+      sw_lib_mmio,
+      sw_lib_runtime_hart,
+    ]
+  )
+)
diff --git a/sw/device/lib/runtime/otbn.c b/sw/device/lib/runtime/otbn.c
new file mode 100644
index 0000000..0761e8d
--- /dev/null
+++ b/sw/device/lib/runtime/otbn.c
@@ -0,0 +1,197 @@
+// 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/otbn.h"
+
+#include "sw/device/lib/dif/dif_otbn.h"
+
+/**
+ * Gets the address in OTBN instruction memory referenced by `ptr`.
+ *
+ * @param ctx The context object.
+ * @param ptr The pointer to convert.
+ * @param[out] func_addr_otbn The address of the function in OTBN's instruction
+ *                            memory.
+ * @return The result of the operation; #kOtbnBadArg if `ptr` is not in the
+ *         instruction memory space of the currently loaded application.
+ */
+static otbn_result_t func_ptr_to_otbn_imem_addr(const otbn_t *ctx,
+                                                otbn_ptr_t ptr,
+                                                uint32_t *imem_addr_otbn) {
+  uintptr_t ptr_addr = (uintptr_t)ptr;
+  uintptr_t app_imem_start_addr = (uintptr_t)ctx->app.imem_start;
+  uintptr_t app_imem_end_addr = (uintptr_t)ctx->app.imem_end;
+
+  if (imem_addr_otbn == NULL || ptr == NULL || ctx == NULL ||
+      ptr_addr < app_imem_start_addr || ptr_addr > app_imem_end_addr) {
+    return kOtbnBadArg;
+  }
+  *imem_addr_otbn = ptr_addr - app_imem_start_addr;
+  return kOtbnOk;
+}
+
+/**
+ * Gets the address in OTBN data memory referenced by `ptr`.
+ *
+ * @param ctx The context object.
+ * @param ptr The pointer to convert.
+ * @param[out] data_addr_otbn The address of the data in OTBN's data memory.
+ * @return The result of the operation; #kOtbnBadArg if `ptr` is not in the data
+ *         memory space of the currently loaded application.
+ */
+static otbn_result_t data_ptr_to_otbn_dmem_addr(const otbn_t *ctx,
+                                                otbn_ptr_t ptr,
+                                                uint32_t *dmem_addr_otbn) {
+  uintptr_t ptr_addr = (uintptr_t)ptr;
+  uintptr_t app_dmem_start_addr = (uintptr_t)ctx->app.dmem_start;
+  uintptr_t app_dmem_end_addr = (uintptr_t)ctx->app.dmem_end;
+
+  if (dmem_addr_otbn == NULL || ptr == NULL || ctx == NULL ||
+      ptr_addr < app_dmem_start_addr || ptr_addr > app_dmem_end_addr) {
+    return kOtbnBadArg;
+  }
+  *dmem_addr_otbn = ptr_addr - app_dmem_start_addr;
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_busy_wait_for_done(otbn_t *ctx) {
+  bool busy = true;
+  while (busy) {
+    if (dif_otbn_is_busy(&ctx->dif, &busy) != kDifOtbnOk) {
+      return kOtbnError;
+    }
+  }
+
+  dif_otbn_err_code_t err_code;
+  if (dif_otbn_get_err_code(&ctx->dif, &err_code) != kDifOtbnOk) {
+    return kOtbnError;
+  }
+  if (err_code != kDifOtbnErrCodeNoError) {
+    return kOtbnExecutionFailed;
+  }
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_init(otbn_t *ctx, const dif_otbn_config_t dif_config) {
+  if (ctx == NULL) {
+    return kOtbnBadArg;
+  }
+
+  ctx->app_is_loaded = false;
+
+  if (dif_otbn_init(&dif_config, &ctx->dif) != kDifOtbnOk) {
+    return kOtbnError;
+  }
+
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_load_app(otbn_t *ctx, const otbn_app_t app) {
+  if (app.imem_end <= app.imem_start || app.dmem_end < app.dmem_start) {
+    return kOtbnBadArg;
+  }
+
+  const size_t imem_size = app.imem_end - app.imem_start;
+  const size_t dmem_size = app.dmem_end - app.dmem_start;
+
+  // Instruction and data memory images must be multiples of 32b words.
+  if (imem_size % sizeof(uint32_t) != 0 || dmem_size % sizeof(uint32_t) != 0) {
+    return kOtbnBadArg;
+  }
+
+  ctx->app_is_loaded = false;
+  ctx->app = app;
+
+  if (dif_otbn_imem_write(&ctx->dif, 0, ctx->app.imem_start, imem_size) !=
+      kDifOtbnOk) {
+    return kOtbnError;
+  }
+
+  if (dmem_size > 0) {
+    if (dif_otbn_dmem_write(&ctx->dif, 0, ctx->app.dmem_start, dmem_size) !=
+        kDifOtbnOk) {
+      return kOtbnError;
+    }
+  }
+
+  ctx->app_is_loaded = true;
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_call_function(otbn_t *ctx, otbn_ptr_t func) {
+  if (ctx == NULL || !ctx->app_is_loaded) {
+    return kOtbnBadArg;
+  }
+
+  uint32_t func_imem_addr;
+  otbn_result_t result = func_ptr_to_otbn_imem_addr(ctx, func, &func_imem_addr);
+  if (result != kOtbnOk) {
+    return result;
+  }
+
+  if (dif_otbn_start(&ctx->dif, func_imem_addr) != kDifOtbnOk) {
+    return kOtbnError;
+  }
+
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_copy_data_to_otbn(otbn_t *ctx, size_t len_bytes,
+                                     const void *src, otbn_ptr_t dest) {
+  if (ctx == NULL || dest == NULL) {
+    return kOtbnBadArg;
+  }
+
+  uint32_t dest_dmem_addr;
+  otbn_result_t result = data_ptr_to_otbn_dmem_addr(ctx, dest, &dest_dmem_addr);
+  if (result != kOtbnOk) {
+    return result;
+  }
+
+  if (dif_otbn_dmem_write(&ctx->dif, dest_dmem_addr, src, len_bytes) !=
+      kDifOtbnOk) {
+    return kOtbnError;
+  }
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_copy_data_from_otbn(otbn_t *ctx, size_t len_bytes,
+                                       otbn_ptr_t src, void *dest) {
+  if (ctx == NULL || dest == NULL) {
+    return kOtbnBadArg;
+  }
+
+  uint32_t src_dmem_addr;
+  otbn_result_t result = data_ptr_to_otbn_dmem_addr(ctx, src, &src_dmem_addr);
+  if (result != kOtbnOk) {
+    return result;
+  }
+
+  if (dif_otbn_dmem_read(&ctx->dif, src_dmem_addr, dest, len_bytes) !=
+      kDifOtbnOk) {
+    return kOtbnError;
+  }
+  return kOtbnOk;
+}
+
+otbn_result_t otbn_zero_data_memory(otbn_t *ctx) {
+  if (ctx == NULL) {
+    return kOtbnBadArg;
+  }
+
+  size_t dmem_size_words =
+      dif_otbn_get_dmem_size_bytes(&ctx->dif) / sizeof(uint32_t);
+  bool retval = kOtbnOk;
+  for (size_t i = 0; i < dmem_size_words; ++i) {
+    const uint32_t zero = 0;
+
+    // Continue the process even if a single write fails to try to clear as much
+    // memory as possible.
+    if (dif_otbn_dmem_write(&ctx->dif, i * sizeof(uint32_t), &zero,
+                            sizeof(zero)) != kDifOtbnOk) {
+      retval = kOtbnError;
+    }
+  }
+  return retval;
+}
diff --git a/sw/device/lib/runtime/otbn.h b/sw/device/lib/runtime/otbn.h
new file mode 100644
index 0000000..48ff8d3
--- /dev/null
+++ b/sw/device/lib/runtime/otbn.h
@@ -0,0 +1,237 @@
+// 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_RUNTIME_OTBN_H_
+#define OPENTITAN_SW_DEVICE_LIB_RUNTIME_OTBN_H_
+
+#include "sw/device/lib/dif/dif_otbn.h"
+
+/**
+ * @file
+ * @brief OpenTitan Big Number Accelerator (OTBN) driver
+ */
+
+/**
+ * Information about an embedded OTBN application image.
+ *
+ * All pointers reference data in the normal CPU address space.
+ *
+ * Use `OTBN_DECLARE_APP_SYMBOLS()` together with `OTBN_APP_T_INIT()` to
+ * initialize this structure.
+ */
+typedef struct otbn_app {
+  /**
+   * Start of OTBN instruction memory.
+   */
+  const uint8_t *imem_start;
+  /**
+   * End of OTBN instruction memory.
+   */
+  const uint8_t *imem_end;
+  /**
+   * Start of OTBN data memory.
+   */
+  const uint8_t *dmem_start;
+  /**
+   * End of OTBN data memory.
+   */
+  const uint8_t *dmem_end;
+} otbn_app_t;
+
+/**
+ * A pointer to a symbol in OTBN's instruction or data memory.
+ *
+ * Use `OTBN_DECLARE_PTR_SYMBOL()` together with `OTBN_PTR_T_INIT()` to
+ * initialize this type.
+ *
+ * The value of the pointer is an address in the normal CPU address space, and
+ * must be first converted into OTBN address space before it can be used there.
+ */
+typedef uint8_t *otbn_ptr_t;
+
+/**
+ * The result of an OTBN operation.
+ */
+typedef enum otbn_result {
+  /**
+   * The operation succeeded.
+   */
+  kOtbnOk = 0,
+  /**
+   * An unspecified failure occurred.
+   */
+  kOtbnError = 1,
+  /**
+   * A precondition check for an argument passed into the function failed.
+   */
+  kOtbnBadArg = 2,
+  /**
+   * The execution of the application on OTBN failed.
+   *
+   * More specific error information can be obtained with
+   * `dif_otbn_get_err_code()`.
+   */
+  kOtbnExecutionFailed = 3,
+} otbn_result_t;
+
+/**
+ * OTBN context structure.
+ *
+ * Use `otbn_init()` to initialize.
+ */
+typedef struct otbn {
+  /**
+   * The OTBN DIF to access the hardware.
+   */
+  dif_otbn_t dif;
+
+  /**
+   * The application loaded or to be loaded into OTBN.
+   *
+   * Only valid if @p app_is_loaded is true.
+   */
+  otbn_app_t app;
+
+  /**
+   * Is the application loaded into OTBN?
+   */
+  bool app_is_loaded;
+} otbn_t;
+
+/**
+ * Makes an embedded OTBN application image available for use.
+ *
+ * Make symbols available that indicate the start and the end of instruction
+ * and data memory regions, as they are stored in the device memory.
+ *
+ * Use this macro instead of manually declaring the symbols as symbol names
+ * might change.
+ *
+ * @param app_name Name of the application to load, which is typically the
+ *                 name of the main (assembly) source file.
+ */
+#define OTBN_DECLARE_APP_SYMBOLS(app_name)                   \
+  extern const uint8_t _otbn_app_##app_name##__imem_start[]; \
+  extern const uint8_t _otbn_app_##app_name##__imem_end[];   \
+  extern const uint8_t _otbn_app_##app_name##__dmem_start[]; \
+  extern const uint8_t _otbn_app_##app_name##__dmem_end[]
+
+/**
+ * Initializes the OTBN application information structure.
+ *
+ * After making all required symbols from the application image available
+ * through `OTBN_DECLARE_APP_SYMBOLS()`, use this macro to initialize an
+ * `otbn_app_t` struct with those symbols.
+ *
+ * @param app_name Name of the application to load.
+ * @see OTBN_DECLARE_APP_SYMBOLS()
+ */
+#define OTBN_APP_T_INIT(app_name)                       \
+  ((otbn_app_t){                                        \
+      .imem_start = _otbn_app_##app_name##__imem_start, \
+      .imem_end = _otbn_app_##app_name##__imem_end,     \
+      .dmem_start = _otbn_app_##app_name##__dmem_start, \
+      .dmem_end = _otbn_app_##app_name##__dmem_end,     \
+  })
+
+/**
+ * Makes a symbol in the OTBN application image available.
+ *
+ * Symbols are typically function or data pointers, i.e. labels in assembly
+ * code.
+ *
+ * Use this macro instead of manually declaring the symbols as symbol names
+ * might change.
+ *
+ * @param app_name    Name of the application the function is contained in.
+ * @param symbol_name Name of the symbol (function, label).
+ */
+#define OTBN_DECLARE_PTR_SYMBOL(app_name, symbol_name) \
+  extern const uint8_t _otbn_app_##app_name##_##symbol_name[]
+
+/**
+ * Initializes an `otbn_ptr_t`.
+ */
+#define OTBN_PTR_T_INIT(app_name, symbol_name) \
+  ((otbn_ptr_t)_otbn_app_##app_name##_##symbol_name)
+
+/**
+ * Initializes the OTBN context structure.
+ *
+ * @param ctx The context object.
+ * @param dif_config The OTBN DIF configuration (passed on to the DIF).
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_init(otbn_t *ctx, const dif_otbn_config_t dif_config);
+
+/**
+ * (Re-)loads the RSA application into OTBN.
+ *
+ * Load the application image with both instruction and data segments into OTBN.
+ *
+ * @param ctx The context object.
+ * @param app The application to load into OTBN.
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_load_app(otbn_t *ctx, const otbn_app_t app);
+
+/**
+ * Calls a function on OTBN.
+ *
+ * Set the entry point (start address) of OTBN to the desired function, and
+ * starts the OTBN operation.
+ *
+ * Use `otbn_busy_wait_for_done()` to wait for the function call to complete.
+ *
+ * @param ctx The context object.
+ * @param func The function to be called.
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_call_function(otbn_t *ctx, otbn_ptr_t func);
+
+/**
+ * Busy waits for OTBN to be done with its operation.
+ *
+ * @param ctx The context object.
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_busy_wait_for_done(otbn_t *ctx);
+
+/**
+ * Copies data from the CPU memory to OTBN data memory.
+ *
+ * @param ctx The context object.
+ * @param len_bytes Number of bytes to copy.
+ * @param dest Pointer to the destination in OTBN's data memory.
+ * @param src Source of the data to copy.
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_copy_data_to_otbn(otbn_t *ctx, size_t len_bytes,
+                                     const void *src, otbn_ptr_t dest);
+
+/**
+ * Copies data from OTBN's data memory to CPU memory.
+ *
+ * @param ctx The context object.
+ * @param len_bytes The number of bytes to copy.
+ * @param src The pointer in OTBN data memory to copy from.
+ * @param[out] dest The destination of the copied data in main memory
+ *                  (preallocated).
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_copy_data_from_otbn(otbn_t *ctx, size_t len_bytes,
+                                       const otbn_ptr_t src, void *dest);
+
+/**
+ * Overwrites all of OTBN's data memory with zeros.
+ *
+ * This function tries to perform the operation for all data words, even if
+ * a single write fails.
+ *
+ * @param ctx The context object.
+ * @return The result of the operation.
+ */
+otbn_result_t otbn_zero_data_memory(otbn_t *ctx);
+
+#endif  // OPENTITAN_SW_DEVICE_LIB_RUNTIME_OTBN_H_