[mask_rom] Create unified error space

1. Create a unified error enum `rom_error_t` which builds up errors from
general categories, the source module and a specific error code.
2. Return such errors from the uart driver.

Note: the name `rom_error_t` was chosen so as to not conflict with `error_t`
defined by C++.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/device/silicon_creator/lib/absl_status.h b/sw/device/silicon_creator/lib/absl_status.h
new file mode 100644
index 0000000..63fb81b
--- /dev/null
+++ b/sw/device/silicon_creator/lib/absl_status.h
@@ -0,0 +1,234 @@
+// 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_ABSL_STATUS_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_ABSL_STATUS_H_
+
+#ifndef USING_ABSL_STATUS
+#error \
+    "Do not include absl_status.h directly or use its status codes.\nInclude error.h and use rom_error_t."
+#endif
+// Note: the status definitions were taken directly from the abseil-cpp
+// library, and in particular from the file:
+// https://github.com/abseil/abseil-cpp/blob/master/absl/status/status.h
+// The copyright is preserved below:
+// -----------------------------------------------------------------------------
+// Copyright 2019 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// -----------------------------------------------------------------------------
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This enum was taken directly from the abseil-cpp library:
+ * https://github.com/abseil/abseil-cpp/blob/master/absl/status/status.h
+ *
+ * These error codes serve as general error classifications which are used to
+ * build up more specific error codes.
+ *
+ * DO NOT USE THESE CODES DIRECTLY. Use these codes to build per-module error
+ * codes in error.h.  Although these error codes are generally used at
+ * Google by RPC servers, the advice about how to use them and how to
+ * categorize errors is generally sound.
+ */
+enum absl_status_code {
+  // StatusCode::kOk
+  //
+  // kOK (gRPC code "OK") does not indicate an error; this value is returned on
+  // success. It is typical to check for this value before proceeding on any
+  // given call across an API or RPC boundary. To check this value, use the
+  // `absl::Status::ok()` member function rather than inspecting the raw code.
+  kOk = 0,
+
+  // StatusCode::kCancelled
+  //
+  // kCancelled (gRPC code "CANCELLED") indicates the operation was cancelled,
+  // typically by the caller.
+  kCancelled = 1,
+
+  // StatusCode::kUnknown
+  //
+  // kUnknown (gRPC code "UNKNOWN") indicates an unknown error occurred. In
+  // general, more specific errors should be raised, if possible. Errors raised
+  // by APIs that do not return enough error information may be converted to
+  // this error.
+  kUnknown = 2,
+
+  // StatusCode::kInvalidArgument
+  //
+  // kInvalidArgument (gRPC code "INVALID_ARGUMENT") indicates the caller
+  // specified an invalid argument, such a malformed filename. Note that such
+  // errors should be narrowly limited to indicate to the invalid nature of the
+  // arguments themselves. Errors with validly formed arguments that may cause
+  // errors with the state of the receiving system should be denoted with
+  // `kFailedPrecondition` instead.
+  kInvalidArgument = 3,
+
+  // StatusCode::kDeadlineExceeded
+  //
+  // kDeadlineExceeded (gRPC code "DEADLINE_EXCEEDED") indicates a deadline
+  // expired before the operation could complete. For operations that may change
+  // state within a system, this error may be returned even if the operation has
+  // completed successfully. For example, a successful response from a server
+  // could have been delayed long enough for the deadline to expire.
+  kDeadlineExceeded = 4,
+
+  // StatusCode::kNotFound
+  //
+  // kNotFound (gRPC code "NOT_FOUND") indicates some requested entity (such as
+  // a file or directory) was not found.
+  //
+  // `kNotFound` is useful if a request should be denied for an entire class of
+  // users, such as during a gradual feature rollout or undocumented allow list.
+  // If, instead, a request should be denied for specific sets of users, such as
+  // through user-based access control, use `kPermissionDenied` instead.
+  kNotFound = 5,
+
+  // StatusCode::kAlreadyExists
+  //
+  // kAlreadyExists (gRPC code "ALREADY_EXISTS") indicates the entity that a
+  // caller attempted to create (such as file or directory) is already present.
+  kAlreadyExists = 6,
+
+  // StatusCode::kPermissionDenied
+  //
+  // kPermissionDenied (gRPC code "PERMISSION_DENIED") indicates that the caller
+  // does not have permission to execute the specified operation. Note that this
+  // error is different than an error due to an *un*authenticated user. This
+  // error code does not imply the request is valid or the requested entity
+  // exists or satisfies any other pre-conditions.
+  //
+  // `kPermissionDenied` must not be used for rejections caused by exhausting
+  // some resource. Instead, use `kResourceExhausted` for those errors.
+  // `kPermissionDenied` must not be used if the caller cannot be identified.
+  // Instead, use `kUnauthenticated` for those errors.
+  kPermissionDenied = 7,
+
+  // StatusCode::kResourceExhausted
+  //
+  // kResourceExhausted (gRPC code "RESOURCE_EXHAUSTED") indicates some resource
+  // has been exhausted, perhaps a per-user quota, or perhaps the entire file
+  // system is out of space.
+  kResourceExhausted = 8,
+
+  // StatusCode::kFailedPrecondition
+  //
+  // kFailedPrecondition (gRPC code "FAILED_PRECONDITION") indicates that the
+  // operation was rejected because the system is not in a state required for
+  // the operation's execution. For example, a directory to be deleted may be
+  // non-empty, an "rmdir" operation is applied to a non-directory, etc.
+  //
+  // Some guidelines that may help a service implementer in deciding between
+  // `kFailedPrecondition`, `kAborted`, and `kUnavailable`:
+  //
+  //  (a) Use `kUnavailable` if the client can retry just the failing call.
+  //  (b) Use `kAborted` if the client should retry at a higher transaction
+  //      level (such as when a client-specified test-and-set fails, indicating
+  //      the client should restart a read-modify-write sequence).
+  //  (c) Use `kFailedPrecondition` if the client should not retry until
+  //      the system state has been explicitly fixed. For example, if an "rmdir"
+  //      fails because the directory is non-empty, `kFailedPrecondition`
+  //      should be returned since the client should not retry unless
+  //      the files are deleted from the directory.
+  kFailedPrecondition = 9,
+
+  // StatusCode::kAborted
+  //
+  // kAborted (gRPC code "ABORTED") indicates the operation was aborted,
+  // typically due to a concurrency issue such as a sequencer check failure or a
+  // failed transaction.
+  //
+  // See the guidelines above for deciding between `kFailedPrecondition`,
+  // `kAborted`, and `kUnavailable`.
+  kAborted = 10,
+
+  // StatusCode::kOutOfRange
+  //
+  // kOutOfRange (gRPC code "OUT_OF_RANGE") indicates the operation was
+  // attempted past the valid range, such as seeking or reading past an
+  // end-of-file.
+  //
+  // Unlike `kInvalidArgument`, this error indicates a problem that may
+  // be fixed if the system state changes. For example, a 32-bit file
+  // system will generate `kInvalidArgument` if asked to read at an
+  // offset that is not in the range [0,2^32-1], but it will generate
+  // `kOutOfRange` if asked to read from an offset past the current
+  // file size.
+  //
+  // There is a fair bit of overlap between `kFailedPrecondition` and
+  // `kOutOfRange`.  We recommend using `kOutOfRange` (the more specific
+  // error) when it applies so that callers who are iterating through
+  // a space can easily look for an `kOutOfRange` error to detect when
+  // they are done.
+  kOutOfRange = 11,
+
+  // StatusCode::kUnimplemented
+  //
+  // kUnimplemented (gRPC code "UNIMPLEMENTED") indicates the operation is not
+  // implemented or supported in this service. In this case, the operation
+  // should not be re-attempted.
+  kUnimplemented = 12,
+
+  // StatusCode::kInternal
+  //
+  // kInternal (gRPC code "INTERNAL") indicates an internal error has occurred
+  // and some invariants expected by the underlying system have not been
+  // satisfied. This error code is reserved for serious errors.
+  kInternal = 13,
+
+  // StatusCode::kUnavailable
+  //
+  // kUnavailable (gRPC code "UNAVAILABLE") indicates the service is currently
+  // unavailable and that this is most likely a transient condition. An error
+  // such as this can be corrected by retrying with a backoff scheme. Note that
+  // it is not always safe to retry non-idempotent operations.
+  //
+  // See the guidelines above for deciding between `kFailedPrecondition`,
+  // `kAborted`, and `kUnavailable`.
+  kUnavailable = 14,
+
+  // StatusCode::kDataLoss
+  //
+  // kDataLoss (gRPC code "DATA_LOSS") indicates that unrecoverable data loss or
+  // corruption has occurred. As this error is serious, proper alerting should
+  // be attached to errors such as this.
+  kDataLoss = 15,
+
+  // StatusCode::kUnauthenticated
+  //
+  // kUnauthenticated (gRPC code "UNAUTHENTICATED") indicates that the request
+  // does not have valid authentication credentials for the operation. Correct
+  // the authentication and try again.
+  kUnauthenticated = 16,
+
+  // StatusCode::DoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_
+  //
+  // NOTE: this error code entry should not be used and you should not rely on
+  // its value, which may change.
+  //
+  // The purpose of this enumerated value is to force people who handle status
+  // codes with `switch()` statements to *not* simply enumerate all possible
+  // values, but instead provide a "default:" case. Providing such a default
+  // case ensures that code will compile when new codes are added.
+  kDoNotUseReservedForFutureExpansionUseDefaultInSwitchInstead_ = 20
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_ABSL_STATUS_H_
diff --git a/sw/device/silicon_creator/lib/drivers/uart.c b/sw/device/silicon_creator/lib/drivers/uart.c
index 8f6a466..9a349f5 100644
--- a/sw/device/silicon_creator/lib/drivers/uart.c
+++ b/sw/device/silicon_creator/lib/drivers/uart.c
@@ -10,6 +10,7 @@
 #include "sw/device/lib/arch/device.h"
 #include "sw/device/lib/base/bitfield.h"
 #include "sw/device/lib/base/mmio.h"
+#include "sw/device/silicon_creator/lib/error.h"
 
 #include "uart_regs.h"  // Generated.
 
@@ -32,15 +33,13 @@
   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.
+rom_error_t uart_init(const uart_t *uart) {
   if (uart == NULL) {
-    return -1;
+    return kErrorUartInvalidArgument;
   }
 
-  // TODO(#34): Change to use unified error space.
   if (uart->baudrate == 0 || uart->clk_freq_hz == 0) {
-    return -1;
+    return kErrorUartInvalidArgument;
   }
 
   // Calculation formula: NCO = 16 * 2^nco_width * baud / fclk.
@@ -53,7 +52,7 @@
   // Requested baudrate is too high for the given clock frequency.
   // TODO(#34): Change to use unified error space.
   if (nco != nco_masked) {
-    return -1;
+    return kErrorUartBadBaudRate;
   }
 
   // Must be called before the first write to any of the UART registers.
@@ -68,7 +67,7 @@
 
   // Disable interrupts.
   mmio_region_write32(uart->base_addr, UART_INTR_ENABLE_REG_OFFSET, 0u);
-  return 0;
+  return kErrorOk;
 }
 
 static bool uart_tx_full(const uart_t *uart) {
diff --git a/sw/device/silicon_creator/lib/drivers/uart.h b/sw/device/silicon_creator/lib/drivers/uart.h
index 4bd9dae..5160b02 100644
--- a/sw/device/silicon_creator/lib/drivers/uart.h
+++ b/sw/device/silicon_creator/lib/drivers/uart.h
@@ -9,6 +9,7 @@
 #include <stdint.h>
 
 #include "sw/device/lib/base/mmio.h"
+#include "sw/device/silicon_creator/lib/error.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -37,9 +38,9 @@
  * 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).
+ * @return kErrorOk if successful, else an error code.
  */
-int uart_init(const uart_t *uart);
+rom_error_t uart_init(const uart_t *uart);
 
 /**
  * Write a single byte to the UART.
diff --git a/sw/device/silicon_creator/lib/drivers/uart_unittest.cc b/sw/device/silicon_creator/lib/drivers/uart_unittest.cc
index 5c0ddce..3d35bb4 100644
--- a/sw/device/silicon_creator/lib/drivers/uart_unittest.cc
+++ b/sw/device/silicon_creator/lib/drivers/uart_unittest.cc
@@ -50,9 +50,8 @@
 class InitTest : public UartTest {};
 
 TEST_F(InitTest, NullArgs) {
-  // FIXME: unified error space.
   // FIXME: add tests with `uart_` misconfigured.
-  EXPECT_EQ(uart_init(nullptr), -1);
+  EXPECT_EQ(uart_init(nullptr), kErrorUartInvalidArgument);
 }
 
 TEST_F(InitTest, Initialize) {
@@ -64,7 +63,7 @@
                                        });
   EXPECT_WRITE32(UART_INTR_ENABLE_REG_OFFSET, 0);
 
-  EXPECT_EQ(uart_init(&uart_), 0);
+  EXPECT_EQ(uart_init(&uart_), kErrorOk);
 }
 
 class BytesSendTest : public UartTest {
diff --git a/sw/device/silicon_creator/lib/error.h b/sw/device/silicon_creator/lib/error.h
new file mode 100644
index 0000000..445b85a
--- /dev/null
+++ b/sw/device/silicon_creator/lib/error.h
@@ -0,0 +1,76 @@
+// 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_ERROR_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_ERROR_H_
+
+#define USING_ABSL_STATUS
+#include "sw/device/silicon_creator/lib/absl_status.h"
+#undef USING_ABSL_STATUS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * List of modules which can produce errors.
+ *
+ * Choose a two-letter identifier for each module and encode the module
+ * identifier the concatenated ASCII representation of those letters.
+ */
+enum module_ {
+  kModuleUnknown = 0,
+  kModuleUart = 0x4155,  // ASCII "UA".
+  kModuleHmac = 0x4d48,  // ASCII "HM".
+};
+
+/**
+ * Helper macro for building up error codes.
+ * @param status_ An appropriate general status code from absl_staus.h.
+ * @param module_ The module identifier which produces this error.
+ * @param error_ The unique error id in that module.  Error ids must not
+ *               repeat within a module.
+ */
+#define ERROR_(error_, module_, status_) \
+  ((error_ << 24) | (module_ << 8) | (status_))
+
+// clang-format off
+// Use an X-macro to facilitate writing unit tests.
+// Note: This list is extend-only and you may not renumber errors after they
+//       have been created.  This is required for error-space stability
+//       after the ROM is frozen.
+#define DEFINE_ERRORS(X) \
+  X(kErrorOk, 0x739), \
+  X(kErrorUartInvalidArgument, ERROR_(1, kModuleUart, kInvalidArgument)), \
+  X(kErrorUartBadBaudRate,     ERROR_(2, kModuleUart, kInvalidArgument)), \
+  X(kErrorUnknown, 0xFFFFFFFF)
+// clang-format on
+
+#define ERROR_ENUM_INIT(name_, value_) name_ = value_
+
+/**
+ * Unified set of errors for Mask ROM and ROM_EXT.
+ */
+typedef enum rom_error {
+  DEFINE_ERRORS(ERROR_ENUM_INIT),
+} rom_error_t;
+
+/**
+ * Evaluate an expression and return if the result is an error.
+ *
+ * @param expr_ An expression which results in an rom_error_t.
+ */
+#define RETURN_IF_ERROR(expr_)        \
+  do {                                \
+    rom_error_t local_error_ = expr_; \
+    if (local_error_ != kErrorOk) {   \
+      return local_error_;            \
+    }                                 \
+  } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_ERROR_H_
diff --git a/sw/device/silicon_creator/lib/error_unittest.cc b/sw/device/silicon_creator/lib/error_unittest.cc
new file mode 100644
index 0000000..0708298
--- /dev/null
+++ b/sw/device/silicon_creator/lib/error_unittest.cc
@@ -0,0 +1,95 @@
+// 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/error.h"
+
+#include <climits>
+#include <cstdint>
+#include <map>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "sw/device/lib/base/bitfield.h"
+
+namespace error_unittest {
+namespace {
+
+// FIXME: what should this value be?
+constexpr int kMinimumHammingDistance = 4;
+
+const std::map<std::string, uint32_t> &GetErrorMap() {
+#define STRINGIFY(x) #x
+#define ERROR_MAP_INIT(name, value) \
+  { STRINGIFY(name), value }
+  static std::map<std::string, uint32_t> errors = {
+      DEFINE_ERRORS(ERROR_MAP_INIT),
+  };
+  return errors;
+}
+
+rom_error_t ReturnIfError(rom_error_t value) {
+  RETURN_IF_ERROR(value);
+
+  // Return something other than kErrorOk just in case RETURN_IF_ERROR
+  // was wrong.
+  return static_cast<rom_error_t>(0);
+}
+
+int HammingDistance(uint32_t a, uint32_t b) {
+  // The hamming distance is the number of bits different between the two words.
+  return bitfield_popcount32(a ^ b);
+}
+
+// Checks that there are no duplicate values in the error definitions.
+TEST(ErrorsTest, NoDuplicateValues) {
+  const auto &errors = GetErrorMap();
+
+  for (auto a = errors.begin(); a != errors.end(); ++a) {
+    for (auto b = a; b != errors.end(); ++b) {
+      if (a == b) {
+        continue;
+      }
+      EXPECT_NE(a->second, b->second)
+          << "Error codes '" << a->first << "' and '" << b->first
+          << "' have the same value.";
+    }
+  }
+}
+
+// Checks that the RETURN_IF_ERROR macro works as expected.
+TEST(ErrorsTest, CheckReturnIfError) {
+  EXPECT_EQ(kErrorUartBadBaudRate, ReturnIfError(kErrorUartBadBaudRate));
+
+  rom_error_t ok = ReturnIfError(kErrorOk);
+  EXPECT_EQ(0, static_cast<int>(ok));
+}
+
+TEST(ErrorsTest, CheckHammingDistanceToOk) {
+  const auto &errors = GetErrorMap();
+  int max_distance = 0;
+  int min_distance = INT_MAX;
+  int distance;
+
+  for (const auto &a : errors) {
+    if (a.second == kErrorOk) {
+      distance = HammingDistance(a.second, 0);
+      std::cout << "Hamming distance between '" << a.first
+                << "' and zero: " << distance << std::endl;
+    } else {
+      distance = HammingDistance(a.second, kErrorOk);
+      std::cout << "Hamming distance between '" << a.first
+                << "' and kErrorOk: " << distance << std::endl;
+    }
+    EXPECT_GE(distance, kMinimumHammingDistance);
+    min_distance = std::min(min_distance, distance);
+    max_distance = std::max(max_distance, distance);
+  }
+  std::cout << "Minimum hamming distance observed: " << min_distance
+            << std::endl;
+  std::cout << "Maximum hamming distance observed: " << max_distance
+            << std::endl;
+}
+
+}  // namespace
+}  // namespace error_unittest
diff --git a/sw/device/silicon_creator/lib/meson.build b/sw/device/silicon_creator/lib/meson.build
index be3b034..79750aa 100644
--- a/sw/device/silicon_creator/lib/meson.build
+++ b/sw/device/silicon_creator/lib/meson.build
@@ -15,3 +15,15 @@
     ],
   ),
 )
+
+test('sw_silicon_creator_lib_error_unittest', executable(
+  'sw_silicon_creator_lib_error_unittest',
+  sources: [
+    'error_unittest.cc',
+  ],
+  dependencies: [
+    sw_vendor_gtest,
+  ],
+  native: true,
+))
+