diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 109a8536..d168dfb7 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -29,6 +29,8 @@ jobs: - 'riscv-semihosting/**' riscv-target-parser: - 'riscv-target-parser/**' + riscv-types: + - 'riscv-types/**' - name: Check for CHANGELOG.md (riscv) if: steps.changes.outputs.riscv == 'true' @@ -77,3 +79,11 @@ jobs: changeLogPath: ./riscv-target-parser/CHANGELOG.md skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-target-parser/CHANGELOG.md file.' + + - name: Check for CHANGELOG.md (riscv-types) + if: steps.changes.outputs.riscv-types == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./riscv-types/CHANGELOG.md + skipLabels: 'skip changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-types/CHANGELOG.md file.' diff --git a/Cargo.toml b/Cargo.toml index d15e82f9..c0505280 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "riscv-rt", "riscv-semihosting", "riscv-target-parser", + "riscv-types", "tests-build", "tests-trybuild", ] @@ -17,4 +18,5 @@ default-members = [ "riscv-peripheral", "riscv-rt", "riscv-semihosting", + "riscv-types", ] diff --git a/riscv-types/CHANGELOG.md b/riscv-types/CHANGELOG.md new file mode 100644 index 00000000..79236008 --- /dev/null +++ b/riscv-types/CHANGELOG.md @@ -0,0 +1,39 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +### Changed + +- Renamed crate to `riscv-types` as per [#351](https://github.com/rust-embedded/riscv/issues/351) + +## riscv-pac [v0.2.0] - 2024-10-19 + +### Added + +- Add `result` module for `Error` and `Result` types +- Add `ExceptionNumber` trait. +- Classify interrupt numbers in `CoreInterruptNumber` and `ExternalInterruptNumber`. +- Added simple tests to illustrate how to implement all the provided traits. + +### Changed + +- All traits now work with `usize` data type. + +## riscv-pac [v0.1.1] - 2024-02-15 + +- Fix crates.io badge links + +## riscv-pac [v0.1.0] - 2024-01-14 + +### Added + +- Add `InterruptNumber`, `PriorityNumber`, and `HartIdNumber` traits. + +### Changed + +- Update `README.md` diff --git a/riscv-types/Cargo.toml b/riscv-types/Cargo.toml new file mode 100644 index 00000000..63a6afc4 --- /dev/null +++ b/riscv-types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "riscv-types" +version = "0.1.0" +edition = "2021" +rust-version = "1.60" +repository = "https://github.com/rust-embedded/riscv" +authors = ["The RISC-V Team "] +categories = ["embedded", "hardware-support", "no-std"] +description = "Low level access to RISC-V processors" +documentation = "https://docs.rs/riscv-types" +keywords = ["riscv", "register", "peripheral"] +license = "ISC" + +[package.metadata.docs.rs] +default-target = "riscv64imac-unknown-none-elf" +targets = [ + "riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", + "riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf", +] diff --git a/riscv-types/README.md b/riscv-types/README.md new file mode 100644 index 00000000..6745b98d --- /dev/null +++ b/riscv-types/README.md @@ -0,0 +1,40 @@ +[![crates.io](https://img.shields.io/crates/d/riscv-types.svg)](https://crates.io/crates/riscv-types) +[![crates.io](https://img.shields.io/crates/v/riscv-types.svg)](https://crates.io/crates/riscv-types) + +# `riscv-types` (previously `riscv-pac`) + +> Target-specific traits to be implemented by PACs + +This project is developed and maintained by the [RISC-V team][team]. + +## [Documentation](https://docs.rs/crate/riscv-types) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Copyright 2023-2025 [RISC-V team][team] + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: ../CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-risc-v-team diff --git a/riscv-types/src/lib.rs b/riscv-types/src/lib.rs new file mode 100644 index 00000000..f0929b3d --- /dev/null +++ b/riscv-types/src/lib.rs @@ -0,0 +1,289 @@ +#![no_std] + +pub mod result; + +use result::Result; + +/// Trait for enums of target-specific exception numbers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// exceptions for a specific device. Alternatively, the `riscv` crate provides a default +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its exception number. +/// +/// # Safety +/// +/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of exceptions. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the exception numbers must be less than or equal to `MAX_EXCEPTION_NUMBER`. +/// * `MAX_EXCEPTION_NUMBER` must coincide with the highest allowed exception number. +pub unsafe trait ExceptionNumber: Copy { + /// Highest number assigned to an exception. + const MAX_EXCEPTION_NUMBER: usize; + + /// Converts an exception to its corresponding number. + fn number(self) -> usize; + + /// Tries to convert a number to a valid exception. + fn from_number(value: usize) -> Result; +} + +/// Trait for enums of target-specific interrupt numbers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// interrupts for a specific device. Alternatively, the `riscv` crate provides a default +/// implementation for the RISC-V ISA. Each variant must convert to a `usize` of its interrupt number. +/// +/// # Safety +/// +/// * This trait must only be implemented on the `riscv` crate or on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of interrupts. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the interrupt numbers must be less than or equal to `MAX_INTERRUPT_NUMBER`. +/// * `MAX_INTERRUPT_NUMBER` must coincide with the highest allowed interrupt number. +pub unsafe trait InterruptNumber: Copy { + /// Highest number assigned to an interrupt source. + const MAX_INTERRUPT_NUMBER: usize; + + /// Converts an interrupt source to its corresponding number. + fn number(self) -> usize; + + /// Tries to convert a number to a valid interrupt. + fn from_number(value: usize) -> Result; +} + +/// Marker trait for enums of target-specific core interrupt numbers. +/// +/// Core interrupts are interrupts are retrieved from the `mcause` CSR. Usually, vectored mode is +/// only available for core interrupts. The `riscv` crate provides a default implementation for +/// the RISC-V ISA. However, a PAC may override the default implementation if the target has a +/// different interrupt numbering scheme (e.g., ESP32C3). +/// +/// # Safety +/// +/// Each enum variant must represent a valid core interrupt number read from the `mcause` CSR. +pub unsafe trait CoreInterruptNumber: InterruptNumber {} + +/// Marker trait for enums of target-specific external interrupt numbers. +/// +/// External interrupts are interrupts caused by external sources (e.g., GPIO, UART, SPI). +/// External interrupts are **not** retrieved from the `mcause` CSR. +/// Instead, RISC-V processors have a single core interrupt for all external interrupts. +/// An additional peripheral (e.g., PLIC) is used to multiplex the external interrupts. +/// +/// # Safety +/// +/// Each enum variant must represent a valid external interrupt number. +pub unsafe trait ExternalInterruptNumber: InterruptNumber {} + +/// Trait for enums of priority levels. +/// +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// priority numbers for a specific device. Each variant must convert to a `usize` of its priority level. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of priority levels. +/// * Each enum variant must represent a distinct value (no duplicates are permitted). +/// * Each enum variant must always return the same value (do not change at runtime). +/// * All the priority level numbers must be less than or equal to `MAX_PRIORITY_NUMBER`. +/// * `MAX_PRIORITY_NUMBER` must coincide with the highest allowed priority number. +pub unsafe trait PriorityNumber: Copy { + /// Number assigned to the highest priority level. + const MAX_PRIORITY_NUMBER: usize; + + /// Converts a priority level to its corresponding number. + fn number(self) -> usize; + + /// Tries to convert a number to a valid priority level. + fn from_number(value: usize) -> Result; +} + +/// Trait for enums of HART identifiers. +/// +/// This trait should be implemented by a peripheral access crate (PAC) on its enum of available +/// HARTs for a specific device. Each variant must convert to a `usize` of its HART ID number. +/// +/// # Safety +/// +/// * This trait must only be implemented on a PAC of a RISC-V target. +/// * This trait must only be implemented on enums of HART IDs. +/// * Each enum variant must represent a distinct value (no duplicates are permitted), +/// * Each anum variant must always return the same value (do not change at runtime). +/// * All the HART ID numbers must be less than or equal to `MAX_HART_ID_NUMBER`. +/// * `MAX_HART_ID_NUMBER` must coincide with the highest allowed HART ID number. +pub unsafe trait HartIdNumber: Copy { + /// Highest number assigned to a context. + const MAX_HART_ID_NUMBER: usize; + + /// Converts a HART ID to its corresponding number. + fn number(self) -> usize; + + /// Tries to convert a number to a valid HART ID. + fn from_number(value: usize) -> Result; +} + +#[cfg(test)] +mod test { + use super::*; + use crate::result::Error; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Exception { + E1 = 1, + E3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Interrupt { + I1 = 1, + I2 = 2, + I4 = 4, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum Priority { + P0 = 0, + P1 = 1, + P2 = 2, + P3 = 3, + } + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + enum HartId { + H0 = 0, + H1 = 1, + H2 = 2, + } + + unsafe impl ExceptionNumber for Exception { + const MAX_EXCEPTION_NUMBER: usize = Self::E3 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Exception::E1), + 3 => Ok(Exception::E3), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl InterruptNumber for Interrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::I4 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 1 => Ok(Interrupt::I1), + 2 => Ok(Interrupt::I2), + 4 => Ok(Interrupt::I4), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl PriorityNumber for Priority { + const MAX_PRIORITY_NUMBER: usize = Self::P3 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(Priority::P0), + 1 => Ok(Priority::P1), + 2 => Ok(Priority::P2), + 3 => Ok(Priority::P3), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + unsafe impl HartIdNumber for HartId { + const MAX_HART_ID_NUMBER: usize = Self::H2 as usize; + + #[inline] + fn number(self) -> usize { + self as _ + } + + #[inline] + fn from_number(number: usize) -> Result { + match number { + 0 => Ok(HartId::H0), + 1 => Ok(HartId::H1), + 2 => Ok(HartId::H2), + _ => Err(Error::InvalidVariant(number)), + } + } + } + + #[test] + fn check_exception_enum() { + assert_eq!(Exception::E1.number(), 1); + assert_eq!(Exception::E3.number(), 3); + + assert_eq!(Exception::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Exception::from_number(1), Ok(Exception::E1)); + assert_eq!(Exception::from_number(2), Err(Error::InvalidVariant(2))); + assert_eq!(Exception::from_number(3), Ok(Exception::E3)); + assert_eq!(Exception::from_number(4), Err(Error::InvalidVariant(4))); + } + + #[test] + fn check_interrupt_enum() { + assert_eq!(Interrupt::I1.number(), 1); + assert_eq!(Interrupt::I2.number(), 2); + assert_eq!(Interrupt::I4.number(), 4); + + assert_eq!(Interrupt::from_number(0), Err(Error::InvalidVariant(0))); + assert_eq!(Interrupt::from_number(1), Ok(Interrupt::I1)); + assert_eq!(Interrupt::from_number(2), Ok(Interrupt::I2)); + assert_eq!(Interrupt::from_number(3), Err(Error::InvalidVariant(3))); + assert_eq!(Interrupt::from_number(4), Ok(Interrupt::I4)); + assert_eq!(Interrupt::from_number(5), Err(Error::InvalidVariant(5))); + } + + #[test] + fn check_priority_enum() { + assert_eq!(Priority::P0.number(), 0); + assert_eq!(Priority::P1.number(), 1); + assert_eq!(Priority::P2.number(), 2); + assert_eq!(Priority::P3.number(), 3); + + assert_eq!(Priority::from_number(0), Ok(Priority::P0)); + assert_eq!(Priority::from_number(1), Ok(Priority::P1)); + assert_eq!(Priority::from_number(2), Ok(Priority::P2)); + assert_eq!(Priority::from_number(3), Ok(Priority::P3)); + assert_eq!(Priority::from_number(4), Err(Error::InvalidVariant(4))); + } + + #[test] + fn check_hart_id_enum() { + assert_eq!(HartId::H0.number(), 0); + assert_eq!(HartId::H1.number(), 1); + assert_eq!(HartId::H2.number(), 2); + + assert_eq!(HartId::from_number(0), Ok(HartId::H0)); + assert_eq!(HartId::from_number(1), Ok(HartId::H1)); + assert_eq!(HartId::from_number(2), Ok(HartId::H2)); + assert_eq!(HartId::from_number(3), Err(Error::InvalidVariant(3))); + } +} diff --git a/riscv-types/src/result.rs b/riscv-types/src/result.rs new file mode 100644 index 00000000..b55c8beb --- /dev/null +++ b/riscv-types/src/result.rs @@ -0,0 +1,58 @@ +use core::fmt; + +/// Convenience alias for the [Result](core::result::Result) type for the library. +pub type Result = core::result::Result; + +/// Represents error variants for the library. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Error { + /// Attempted out-of-bounds access. + IndexOutOfBounds { + index: usize, + min: usize, + max: usize, + }, + /// Invalid field value. + InvalidFieldValue { + field: &'static str, + value: usize, + bitmask: usize, + }, + /// Invalid value of a register field that does not match any known variants. + InvalidFieldVariant { field: &'static str, value: usize }, + /// Invalid value. + InvalidValue { value: usize, bitmask: usize }, + /// Invalid value that does not match any known variants. + InvalidVariant(usize), + /// Unimplemented function or type. + Unimplemented, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::IndexOutOfBounds { index, min, max } => write!( + f, + "out-of-bounds access, index: {index}, min: {min}, max: {max}" + ), + Self::InvalidFieldValue { + field, + value, + bitmask, + } => write!( + f, + "invalid {field} field value: {value:#x}, valid bitmask: {bitmask:#x}", + ), + Self::InvalidFieldVariant { field, value } => { + write!(f, "invalid {field} field variant: {value:#x}") + } + Self::InvalidValue { value, bitmask } => { + write!(f, "invalid value: {value:#x}, valid bitmask: {bitmask:#x}",) + } + Self::InvalidVariant(value) => { + write!(f, "invalid variant: {value:#x}") + } + Self::Unimplemented => write!(f, "unimplemented"), + } + } +}