util: precise errors infrastructure Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/util/error/BUILD.bazel b/util/error/BUILD.bazel new file mode 100644 index 0000000..eb7594e --- /dev/null +++ b/util/error/BUILD.bazel
@@ -0,0 +1,26 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "error", + srcs = [ + "flash.rs", + "ipc.rs", + "kernel.rs", + "lib.rs", + ], + crate_name = "util_error", + edition = "2024", + deps = [ + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_doc( + name = "error_doc", + crate = ":error", +)
diff --git a/util/error/README.md b/util/error/README.md new file mode 100644 index 0000000..94dced5 --- /dev/null +++ b/util/error/README.md
@@ -0,0 +1,62 @@ +# util_error + +Structured error handling for OpenProt. + +This crate provides a mechanism for defining and using structured 32-bit error codes (`ErrorCode`) partitioned by 16-bit modules (`ErrorModule`). + +## Key Concepts + +### ErrorModule + +An `ErrorModule` is a 16-bit identifier that categorizes a set of error codes. It is recommended to use ASCII characters for the module ID to aid in debugging. + +```rust +use util_error::ErrorModule; + +// Define a module with ID 'MY' (0x4d59) +pub const MY_MODULE: ErrorModule = ErrorModule::new(0x4d59); +``` + +### ErrorCode + +An `ErrorCode` is a 32-bit value composed of: +* Upper 16 bits: The `ErrorModule` ID. +* Lower 16 bits: A module-specific error value. + +`ErrorCode` implements `core::error::Error`, `Display`, and `Debug`. It formats as a hex representation of the 32-bit value (e.g., `0x4b450001`). + +```rust +use util_error::ErrorCode; + +// Create an error code under MY_MODULE +pub const MY_ERROR: ErrorCode = MY_MODULE.error(1); +``` + +### Pigweed Integration + +`ErrorCode` supports integration with `pw_status::Error`. You can embed a Pigweed status into the lower 16 bits of the error code using `from_pw`. + +The lower 16 bits are partitioned as: +* Bits 5-15: Module-specific error code. +* Bits 0-4: Pigweed `pw_status::Error` (which is 5 bits). + +```rust +use util_error::ErrorModule; +use pw_status::Error; + +pub const MY_MODULE: ErrorModule = ErrorModule::new(0x4d59); + +// Create an error code that embeds pw_status::Error::InvalidArgument +pub const MY_INVALID_ARG_ERROR: ErrorCode = MY_MODULE.from_pw(1, Error::InvalidArgument); +``` + +## Defined Modules + +The following modules are defined in this crate: + +| Module | ID (Hex) | ASCII | Description | +| :--- | :--- | :--- | :--- | +| `KERNEL_ERROR` | `0x4b45` | `KE` | Kernel-specific error codes (see [kernel.rs](file:///usr/local/google/home/cfrantz/src/openprot/errors/util/error/kernel.rs)). | +| `FLASH_GENERIC` | `0x464c` | `FL` | Generic flash and SFDP errors (see [flash.rs](file:///usr/local/google/home/cfrantz/src/openprot/errors/util/error/flash.rs)). | +| `FLASH_OPENTITAN`| `0x464f` | `FO` | OpenTitan-specific flash errors (see [flash.rs](file:///usr/local/google/home/cfrantz/src/openprot/errors/util/error/flash.rs)). | +| `IPC_ERROR` | `0x4943` | `IC` | IPC-specific error codes (see [ipc.rs](file:///usr/local/google/home/cfrantz/src/openprot/errors/util/error/ipc.rs)). |
diff --git a/util/error/flash.rs b/util/error/flash.rs new file mode 100644 index 0000000..976bfb4 --- /dev/null +++ b/util/error/flash.rs
@@ -0,0 +1,63 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Flash-specific error codes. + +use crate::{ErrorCode, ErrorModule}; +use pw_status::Error; + +// TODO: review the pw_status error codes. + +/// The generic flash error module. +pub const FLASH_GENERIC: ErrorModule = ErrorModule::new(0x464c); //ascii `FL`. +/// The flash device is busy. +pub const FLASH_GENERIC_BUSY: ErrorCode = FLASH_GENERIC.from_pw(0, Error::Unavailable); +/// The erase address is invalid (e.g., not page-aligned). +pub const FLASH_GENERIC_ERASE_INVALID_ADDR: ErrorCode = + FLASH_GENERIC.from_pw(1, Error::InvalidArgument); +/// The operation has bad alignment. +pub const FLASH_GENERIC_BAD_ALIGNMENT: ErrorCode = FLASH_GENERIC.from_pw(2, Error::InvalidArgument); +/// The read request is too long. +pub const FLASH_GENERIC_READ_TOO_LONG: ErrorCode = FLASH_GENERIC.from_pw(3, Error::InvalidArgument); +/// The program operation exceeds the hardware window size. +pub const FLASH_GENERIC_PROGRAM_EXCEEDS_WINDOW_SIZE: ErrorCode = + FLASH_GENERIC.from_pw(4, Error::InvalidArgument); +/// The program operation spans a hardware window boundary. +pub const FLASH_GENERIC_PROGRAM_SPANS_WINDOW_BOUNDARY: ErrorCode = + FLASH_GENERIC.from_pw(5, Error::InvalidArgument); +/// The address is out of bounds. +pub const FLASH_GENERIC_ADDR_OUT_OF_BOUNDS: ErrorCode = + FLASH_GENERIC.from_pw(6, Error::InvalidArgument); +/// The flash page size is invalid. +pub const FLASH_GENERIC_INVALID_PAGE_SIZE: ErrorCode = + FLASH_GENERIC.from_pw(7, Error::InvalidArgument); +/// The flash size is invalid. +pub const FLASH_GENERIC_INVALID_SIZE: ErrorCode = FLASH_GENERIC.from_pw(8, Error::InvalidArgument); +/// The erase size is invalid. +pub const FLASH_GENERIC_ERASE_INVALID_SIZE: ErrorCode = + FLASH_GENERIC.from_pw(9, Error::InvalidArgument); + +/// SFDP: Invalid memory density. +pub const FLASH_GENERIC_SFDP_INVALID_MEMORY_DENSITY: ErrorCode = + FLASH_GENERIC.from_pw(1024, Error::InvalidArgument); +/// SFDP: Invalid signature. +pub const FLASH_GENERIC_SFDP_INVALID_SIGNATURE: ErrorCode = + FLASH_GENERIC.from_pw(1025, Error::InvalidArgument); +/// SFDP: No valid parameter header found. +pub const FLASH_GENERIC_SFDP_NO_VALID_PARAMETER_HEADER_FOUND: ErrorCode = + FLASH_GENERIC.from_pw(1026, Error::InvalidArgument); +/// SFDP: Parameters are too short. +pub const FLASH_GENERIC_SFDP_PARAMETERS_TOO_SHORT: ErrorCode = + FLASH_GENERIC.from_pw(1027, Error::InvalidArgument); +/// SFDP: Unsupported header major revision. +pub const FLASH_GENERIC_SFDP_UNSUPPORTED_HEADER_MAJOR_REV: ErrorCode = + FLASH_GENERIC.from_pw(1028, Error::InvalidArgument); +/// SFDP: Unsupported parameters major revision. +pub const FLASH_GENERIC_SFDP_UNSUPPORTED_PARAMS_MAJOR_REV: ErrorCode = + FLASH_GENERIC.from_pw(1029, Error::InvalidArgument); +/// SFDP: Parameters are too long. +pub const FLASH_GENERIC_SFDP_PARAMETERS_TOO_LONG: ErrorCode = + FLASH_GENERIC.from_pw(1030, Error::InvalidArgument); + +/// The OpenTitan flash error module. +pub const FLASH_OPENTITAN: ErrorModule = ErrorModule::new(0x464f); //ascii `FO`.
diff --git a/util/error/ipc.rs b/util/error/ipc.rs new file mode 100644 index 0000000..e86db79 --- /dev/null +++ b/util/error/ipc.rs
@@ -0,0 +1,20 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! IPC-specific error codes. + +use crate::{ErrorCode, ErrorModule}; +use pw_status::Error; + +/// The IPC error module. +pub const IPC_ERROR: ErrorModule = ErrorModule::new(0x4943); // ascii `IC` +/// The IPC response has a bad length. +pub const IPC_ERROR_RSP_BAD_LEN: ErrorCode = IPC_ERROR.from_pw(1, Error::InvalidArgument); +/// The IPC response is too large. +pub const IPC_ERROR_RSP_TOO_LARGE: ErrorCode = IPC_ERROR.from_pw(2, Error::InvalidArgument); +/// The IPC request is bad. +pub const IPC_ERROR_BAD_REQ: ErrorCode = IPC_ERROR.from_pw(3, Error::InvalidArgument); +/// The IPC request has a bad length. +pub const IPC_ERROR_BAD_REQ_LEN: ErrorCode = IPC_ERROR.from_pw(4, Error::InvalidArgument); +/// The IPC opcode is unknown. +pub const IPC_ERROR_UNKNOWN_OP: ErrorCode = IPC_ERROR.from_pw(5, Error::Unknown);
diff --git a/util/error/kernel.rs b/util/error/kernel.rs new file mode 100644 index 0000000..f0f82db --- /dev/null +++ b/util/error/kernel.rs
@@ -0,0 +1,41 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Kernel-specific error codes. + +use crate::{ErrorCode, ErrorModule}; + +/// The kernel error module. +pub const KERNEL_ERROR: ErrorModule = ErrorModule::new(0x4b45); // ascii `KE` +/// The operation was cancelled. +pub const KERNEL_ERROR_CANCELLED: ErrorCode = KERNEL_ERROR.error(1); +/// An unknown error occurred in the kernel. +pub const KERNEL_ERROR_UNKNOWN: ErrorCode = KERNEL_ERROR.error(2); +/// An invalid argument was provided to a kernel call. +pub const KERNEL_ERROR_INVALID_ARGUMENT: ErrorCode = KERNEL_ERROR.error(3); +/// The deadline for the operation was exceeded. +pub const KERNEL_ERROR_DEADLINE_EXCEEDED: ErrorCode = KERNEL_ERROR.error(4); +/// The requested resource was not found. +pub const KERNEL_ERROR_NOT_FOUND: ErrorCode = KERNEL_ERROR.error(5); +/// The resource already exists. +pub const KERNEL_ERROR_ALREADY_EXISTS: ErrorCode = KERNEL_ERROR.error(6); +/// Permission was denied for the operation. +pub const KERNEL_ERROR_PERMISSION_DENIED: ErrorCode = KERNEL_ERROR.error(7); +/// Resources have been exhausted. +pub const KERNEL_ERROR_RESOURCE_EXHAUSTED: ErrorCode = KERNEL_ERROR.error(8); +/// A precondition for the operation failed. +pub const KERNEL_ERROR_FAILED_PRECONDITION: ErrorCode = KERNEL_ERROR.error(9); +/// The operation was aborted. +pub const KERNEL_ERROR_ABORTED: ErrorCode = KERNEL_ERROR.error(10); +/// The value is out of range. +pub const KERNEL_ERROR_OUT_OF_RANGE: ErrorCode = KERNEL_ERROR.error(11); +/// The operation is unimplemented. +pub const KERNEL_ERROR_UNIMPLEMENTED: ErrorCode = KERNEL_ERROR.error(12); +/// An internal kernel error occurred. +pub const KERNEL_ERROR_INTERNAL: ErrorCode = KERNEL_ERROR.error(13); +/// The service or resource is unavailable. +pub const KERNEL_ERROR_UNAVAILABLE: ErrorCode = KERNEL_ERROR.error(14); +/// Data loss has occurred. +pub const KERNEL_ERROR_DATA_LOSS: ErrorCode = KERNEL_ERROR.error(15); +/// The caller is unauthenticated. +pub const KERNEL_ERROR_UNAUTHENTICATED: ErrorCode = KERNEL_ERROR.error(16);
diff --git a/util/error/lib.rs b/util/error/lib.rs new file mode 100644 index 0000000..f509408 --- /dev/null +++ b/util/error/lib.rs
@@ -0,0 +1,122 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Error code handling. + +#![no_std] + +use core::num::NonZero; + +mod flash; +mod ipc; +mod kernel; + +pub use flash::*; +pub use ipc::*; +pub use kernel::*; + +/// Represents an error module. +/// +/// An error module is a non-zero 16-bit identifier that categorizes a set of +/// error codes. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ErrorModule(pub NonZero<u16>); + +impl ErrorModule { + /// Creates a new `ErrorModule`. + /// + /// # Panics + /// Panics if `val` is zero. + pub const fn new(val: u16) -> Self { + match NonZero::new(val) { + Some(val) => Self(val), + None => panic!("ErrorModule must be non-zero"), + } + } + + /// Creates an `ErrorCode` within this module. + /// + /// The resulting `ErrorCode` will have the module ID in the upper 16 bits + /// and the provided `code` in the lower 16 bits. + pub const fn error(self, code: u16) -> ErrorCode { + ErrorCode::new(((self.0.get() as u32) << 16) | (code as u32)) + } + + /// Creates an `ErrorCode` from a Pigweed status. + /// + /// This is a convenience method for creating error codes that incorporate + /// a Pigweed status. + pub const fn from_pw(self, code: u16, err: pw_status::Error) -> ErrorCode { + // pw_status::Error is 5 bits. + self.error((code << 5) | (err as u16)) + } +} + +/// A 32-bit error code. +/// +/// An error code consists of a 16-bit module ID and a 16-bit module-specific +/// error value. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ErrorCode(pub NonZero<u32>); + +impl ErrorCode { + /// Creates a new `ErrorCode`. + /// + /// # Panics + /// Panics if `val` is zero. + pub const fn new(val: u32) -> Self { + match NonZero::new(val) { + Some(val) => Self(val), + None => panic!("ErrorCode must be non-zero"), + } + } + + /// Creates a kernel error code from a Pigweed status. + pub fn kernel_error(e: pw_status::Error) -> Self { + KERNEL_ERROR.error(e as u16) + } +} + +impl From<ErrorCode> for u32 { + fn from(e: ErrorCode) -> u32 { + e.0.get() + } +} + +/* + * TODO: decide if we want ufmt or not. +use ufmt::{uDebug, uDisplay, uwrite}; +impl uDisplay for ErrorCode { + fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + uwrite!(f, "0x{:x}", self.0.get()) + } +} + +impl uDebug for ErrorCode { + fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + uDisplay::fmt(self, f) + } +} +*/ + +impl core::fmt::Display for ErrorCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "0x{:x}", self.0.get()) + } +} + +impl core::fmt::Debug for ErrorCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } +} + +impl core::error::Error for ErrorCode {}