[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,
+))
+