diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 109a8536..8081f901 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -19,6 +19,8 @@ jobs: filters: | riscv: - 'riscv/**' + riscv-macros: + - 'riscv-macros/**' riscv-pac: - 'riscv-pac/**' riscv-peripheral: @@ -29,6 +31,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' @@ -37,6 +41,14 @@ jobs: changeLogPath: ./riscv/CHANGELOG.md skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv/CHANGELOG.md file.' + + - name: Check for CHANGELOG.md (riscv-macros) + if: steps.changes.outputs.riscv-macros == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./riscv-macros/CHANGELOG.md + skipLabels: 'skip changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-macros/CHANGELOG.md file.' - name: Check for CHANGELOG.md (riscv-pac) if: steps.changes.outputs.riscv-pac == 'true' @@ -77,3 +89,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/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml new file mode 100644 index 00000000..805f00dd --- /dev/null +++ b/.github/workflows/qemu.yaml @@ -0,0 +1,59 @@ +name: QEMU tests +on: + merge_group: + pull_request: + push: + branches: + - master + +env: + CARGO_TERM_COLOR: always + +jobs: + testexamples: + name: QEMU run + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + toolchain: [stable] + target-qemu: + - target: riscv32i-unknown-none-elf + qemu: riscv32 + - target: riscv32im-unknown-none-elf + qemu: riscv32 + - target: riscv32imc-unknown-none-elf + qemu: riscv32 + - target: riscv32imac-unknown-none-elf + qemu: riscv32 + - target: riscv32imafc-unknown-none-elf + qemu: riscv32 + - target: riscv64imac-unknown-none-elf + qemu: riscv64 + - target: riscv64gc-unknown-none-elf + qemu: riscv64 + example: + - qemu_uart + - qemu_semihosting + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure Rust target ${{ matrix.target-qemu.target }} + run: | + rustup toolchain install ${{ matrix.toolchain }} + rustup default ${{ matrix.toolchain }} + rustup target add ${{ matrix.target-qemu.target }} + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Install QEMU + run: | + sudo apt update + sudo apt install -y qemu-system-${{ matrix.target-qemu.qemu }} + + - name: Run-pass tests + run: cargo run --package xtask -- qemu --target ${{ matrix.target-qemu.target }} --example ${{ matrix.example }} + diff --git a/.github/workflows/riscv-rt.yaml b/.github/workflows/riscv-rt.yaml index 094d25a1..da2e7940 100644 --- a/.github/workflows/riscv-rt.yaml +++ b/.github/workflows/riscv-rt.yaml @@ -10,8 +10,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.67.0 - toolchain: [ stable, nightly, 1.67.0 ] + # All generated code should be running on stable now, MRSV is 1.68.0 + toolchain: [ stable, nightly, 1.68.0 ] target: - riscv32i-unknown-none-elf - riscv32im-unknown-none-elf @@ -28,9 +28,9 @@ jobs: - toolchain: nightly experimental: true exclude: - - toolchain: 1.67.0 + - toolchain: 1.68.0 target: riscv32im-unknown-none-elf - - toolchain: 1.67.0 + - toolchain: 1.68.0 target: riscv32imafc-unknown-none-elf runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || false }} diff --git a/.github/workflows/riscv.yaml b/.github/workflows/riscv.yaml index cf8a1e94..0a7ffc2b 100644 --- a/.github/workflows/riscv.yaml +++ b/.github/workflows/riscv.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.67.0 - toolchain: [ stable, nightly, 1.67.0 ] + # All generated code should be running on stable now, MRSV is 1.68.0 + toolchain: [ stable, nightly, 1.68.0 ] target: - riscv32i-unknown-none-elf - riscv32imc-unknown-none-elf @@ -40,7 +40,7 @@ jobs: - name: Build (all features) run: cargo build --package riscv --target ${{ matrix.target }} --all-features - # On MacOS, Ubuntu, and Windows, we at least make sure that the crate builds and links. + # On MacOS, Ubuntu, and Windows, we run tests. build-others: strategy: matrix: @@ -49,10 +49,10 @@ jobs: steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - - name: Build (no features) - run: cargo build --package riscv - - name: Build (all features) - run: cargo build --package riscv --all-features + - name: Test (no features) + run: cargo test --package riscv + - name: Test (all features) + run: cargo test --package riscv --all-features # Job to check that all the builds succeeded build-check: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2393f742..954efc2a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,8 +20,8 @@ jobs: run-build: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.67.0 - toolchain: [ stable, nightly, 1.67.0 ] + # All generated code should be running on stable now, MRSV is 1.68.0 + toolchain: [ stable, nightly, 1.68.0 ] target: - riscv32i-unknown-none-elf - riscv32im-unknown-none-elf @@ -37,9 +37,9 @@ jobs: - toolchain: nightly experimental: true exclude: - - toolchain: 1.67.0 + - toolchain: 1.68.0 target: riscv32im-unknown-none-elf - - toolchain: 1.67.0 + - toolchain: 1.68.0 target: riscv32imafc-unknown-none-elf runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || false }} diff --git a/Cargo.toml b/Cargo.toml index d15e82f9..65b34667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,19 +2,24 @@ resolver = "2" members = [ "riscv", + "riscv-macros", "riscv-pac", "riscv-peripheral", "riscv-rt", "riscv-semihosting", "riscv-target-parser", + "riscv-types", "tests-build", "tests-trybuild", + "xtask", ] default-members = [ "riscv", + "riscv-macros", "riscv-pac", "riscv-peripheral", "riscv-rt", "riscv-semihosting", + "riscv-types", ] diff --git a/LICENSE-APACHE.md b/LICENSE-APACHE.md new file mode 100644 index 00000000..16fe87b0 --- /dev/null +++ b/LICENSE-APACHE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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 + + http://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. diff --git a/LICENSE-MIT.md b/LICENSE-MIT.md new file mode 100644 index 00000000..210c8349 --- /dev/null +++ b/LICENSE-MIT.md @@ -0,0 +1,25 @@ +Copyright (c) 2018-2025 The Rust-Embedded Working Group + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/ci/expected/qemu_semihosting.run b/ci/expected/qemu_semihosting.run new file mode 100644 index 00000000..6df362d2 --- /dev/null +++ b/ci/expected/qemu_semihosting.run @@ -0,0 +1 @@ +Hello from semihosting! diff --git a/ci/expected/qemu_uart.run b/ci/expected/qemu_uart.run new file mode 100644 index 00000000..4687b774 --- /dev/null +++ b/ci/expected/qemu_uart.run @@ -0,0 +1 @@ +Hello from UART! diff --git a/riscv-macros/CHANGELOG.md b/riscv-macros/CHANGELOG.md new file mode 100644 index 00000000..f1109e1a --- /dev/null +++ b/riscv-macros/CHANGELOG.md @@ -0,0 +1,23 @@ +# 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] + +### Added + +- New `rt` and `rt-v-trap` features to opt-in `riscv-rt`-related code in `riscv::pac_enum` macro. + +### Changed + +- Fix `cargo doc` errors. +- Use fully qualified paths in generated code (i.e., `::riscv` instead of `riscv`) +- Moved from `riscv/macros/` to `riscv-macros/` +- Now, `riscv::pac_enum` macro only includes trap-related code if `rt` or `rt-v-trap` features are enabled. + +## [v0.3.0] - 2025-09-08 + +This crate was placed inside `riscv/`. Check `riscv/CHANGELOG.md` for details diff --git a/riscv/macros/Cargo.toml b/riscv-macros/Cargo.toml similarity index 84% rename from riscv/macros/Cargo.toml rename to riscv-macros/Cargo.toml index 43773c7a..5a9bf252 100644 --- a/riscv/macros/Cargo.toml +++ b/riscv-macros/Cargo.toml @@ -4,7 +4,7 @@ authors = [ ] categories = ["embedded", "no-std"] description = "Procedural macros re-exported in `riscv`" -documentation = "https://docs.rs/riscv" +documentation = "https://docs.rs/riscv-macros" keywords = ["riscv", "register", "peripheral"] license = "MIT OR Apache-2.0" name = "riscv-macros" @@ -15,6 +15,10 @@ edition = "2021" [lib] proc-macro = true +[features] +rt = [] +rt-v-trap = ["rt"] + [dependencies] proc-macro2 = "1.0" quote = "1.0" diff --git a/riscv-macros/README.md b/riscv-macros/README.md new file mode 100644 index 00000000..cf5844e9 --- /dev/null +++ b/riscv-macros/README.md @@ -0,0 +1,40 @@ +[![crates.io](https://img.shields.io/crates/d/riscv-macros.svg)](https://crates.io/crates/riscv-macros) +[![crates.io](https://img.shields.io/crates/v/riscv-macros.svg)](https://crates.io/crates/riscv-macros) + +# `riscv-macros` + +> Procedural macros for the `riscv` crate. + +This project is developed and maintained by the [RISC-V team][team]. + +## [Documentation](https://docs.rs/crate/riscv) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Copyright 2024-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/macros/src/lib.rs b/riscv-macros/src/lib.rs similarity index 81% rename from riscv/macros/src/lib.rs rename to riscv-macros/src/lib.rs index 5f4bf8f3..70830506 100644 --- a/riscv/macros/src/lib.rs +++ b/riscv-macros/src/lib.rs @@ -11,6 +11,7 @@ use syn::{ }; /// Struct to represent a function parameter. +#[cfg(feature = "rt")] struct FunctionParam { /// Name of the parameter. param_name: TokenStream2, @@ -20,6 +21,7 @@ struct FunctionParam { /// Configuration parameters of a trap. It is useful to abstract the /// differences between exception handlers and core interrupt handlers. +#[cfg(feature = "rt")] struct TrapConfig { /// Name of the default handler (e.g., `DefaultHandler` for core interrupts). default_handler: TokenStream2, @@ -31,6 +33,7 @@ struct TrapConfig { handlers_array_name: TokenStream2, } +#[cfg(feature = "rt")] impl TrapConfig { /// Vector with all the input parameters expected when declaring extern handler functions fn extern_signature(&self) -> Vec { @@ -107,6 +110,7 @@ impl PacTrait { } /// For Exception or an Interrupt enums, it returns the trap configuration details. + #[cfg(feature = "rt")] fn trap_config(&self) -> Option { match self { Self::Exception => Some(TrapConfig { @@ -163,6 +167,7 @@ impl InterruptType { } /// Returns a token stream representing the name of the array of interrupt service routines + #[cfg(feature = "rt")] fn isr_array_name(&self) -> TokenStream2 { match self { Self::Core => quote!(__CORE_INTERRUPTS), @@ -171,6 +176,7 @@ impl InterruptType { } /// Returns a token stream representing the name of the interrupt dispatch function + #[cfg(feature = "rt")] fn dispatch_fn_name(&self) -> TokenStream2 { match self { Self::Core => quote!(_dispatch_core_interrupt), @@ -239,6 +245,7 @@ impl PacEnumItem { } /// Returns a vector of token streams representing the interrupt handler functions + #[cfg(feature = "rt")] fn handlers(&self, trap_config: &TrapConfig) -> Vec { let signature = trap_config.extern_signature(); self.numbers @@ -252,6 +259,7 @@ impl PacEnumItem { /// Returns a sorted vector of token streams representing all the elements of the interrupt array. /// If an interrupt number is not present in the enum, the corresponding element is `None`. /// Otherwise, it is `Some()`. + #[cfg(feature = "rt")] fn handlers_array(&self) -> Vec { let mut vectors = vec![]; for i in 0..=self.max_number { @@ -264,6 +272,7 @@ impl PacEnumItem { vectors } + #[cfg(feature = "rt-v-trap")] fn vector_table(&self) -> TokenStream2 { let align = match std::env::var("RISCV_MTVEC_ALIGN") { Ok(x) => x.parse::().ok(), @@ -280,7 +289,7 @@ impl PacEnumItem { }; let mut asm = format!( r#" -#[cfg(all(feature = "v-trap", any(target_arch = "riscv32", target_arch = "riscv64")))] +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] core::arch::global_asm!(" .section .trap.vector, \"ax\" .global _vector_table @@ -328,11 +337,9 @@ core::arch::global_asm!(" let max_discriminant = self.max_number; let valid_matches = self.valid_matches(); - let is_core_interrupt = matches!(attr, PacTrait::Interrupt(InterruptType::Core)); - // Push the trait implementation res.push(quote! { - unsafe impl riscv::#trait_name for #name { + unsafe impl ::riscv::#trait_name for #name { const #const_name: usize = #max_discriminant; #[inline] @@ -341,67 +348,64 @@ core::arch::global_asm!(" } #[inline] - fn from_number(number: usize) -> riscv::result::Result { + fn from_number(number: usize) -> ::riscv::result::Result { match number { #(#valid_matches,)* - _ => Err(riscv::result::Error::InvalidVariant(number)), + _ => Err(::riscv::result::Error::InvalidVariant(number)), } } } }); if let Some(marker_trait_name) = attr.marker_trait_name() { - res.push(quote! { unsafe impl riscv::#marker_trait_name for #name {} }); + res.push(quote! { unsafe impl ::riscv::#marker_trait_name for #name {} }); } + #[cfg(feature = "rt")] if let Some(trap_config) = attr.trap_config() { - let default_handler = &trap_config.default_handler; - let extern_signature = trap_config.extern_signature(); - let handler_input = trap_config.handler_input(); - let array_signature = trap_config.array_signature(); - let dispatch_fn_name = &trap_config.dispatch_fn_name; - let dispatch_fn_args = &trap_config.dispatch_fn_signature(); - let vector_table = &trap_config.handlers_array_name; - - let handlers = self.handlers(&trap_config); - let interrupt_array = self.handlers_array(); - let cfg_v_trap = match is_core_interrupt { - true => Some(quote!(#[cfg(not(feature = "v-trap"))])), - false => None, - }; - - // Push the interrupt handler functions and the interrupt array - res.push(quote! { - #cfg_v_trap - extern "C" { - #(#handlers;)* + match attr { + #[cfg(feature = "rt-v-trap")] + PacTrait::Interrupt(InterruptType::Core) => { + res.push(self.vector_table()); } - - #cfg_v_trap - #[doc(hidden)] - #[no_mangle] - pub static #vector_table: [Option; #max_discriminant + 1] = [ - #(#interrupt_array),* - ]; - - #cfg_v_trap - #[inline] - #[no_mangle] - unsafe extern "C" fn #dispatch_fn_name(#(#dispatch_fn_args),*) { - extern "C" { - fn #default_handler(#(#extern_signature),*); - } - - match #vector_table.get(code) { - Some(Some(handler)) => handler(#(#handler_input),*), - _ => #default_handler(#(#handler_input),*), - } + _ => { + let default_handler = &trap_config.default_handler; + let extern_signature = trap_config.extern_signature(); + let handler_input = trap_config.handler_input(); + let array_signature = trap_config.array_signature(); + let dispatch_fn_name = &trap_config.dispatch_fn_name; + let dispatch_fn_args = &trap_config.dispatch_fn_signature(); + let vector_table = &trap_config.handlers_array_name; + + let handlers = self.handlers(&trap_config); + let interrupt_array = self.handlers_array(); + + res.push(quote! { + extern "C" { + #(#handlers;)* + } + + #[doc(hidden)] + #[no_mangle] + pub static #vector_table: [Option; #max_discriminant + 1] = [ + #(#interrupt_array),* + ]; + + #[inline] + #[no_mangle] + unsafe extern "C" fn #dispatch_fn_name(#(#dispatch_fn_args),*) { + extern "C" { + fn #default_handler(#(#extern_signature),*); + } + + match #vector_table.get(code) { + Some(Some(handler)) => handler(#(#handler_input),*), + _ => #default_handler(#(#handler_input),*), + } + } + }); } - }); - } - - if is_core_interrupt { - res.push(self.vector_table()); + } } res @@ -413,8 +417,8 @@ core::arch::global_asm!(" /// As these traits are unsafe, the macro must be called with the `unsafe` keyword followed by the trait name. /// In this way, we warn callers that they must comply with the requirements of the trait. /// -/// The trait name must be one of `ExceptionNumber`, `InterruptNumber`, `PriorityNumber`, or `HartIdNumber`. -/// Marker traits `CoreInterruptNumber` and `ExternalInterruptNumber` cannot be implemented using this macro. +/// The trait name must be one of `ExceptionNumber`, `CoreInterruptNumber`, `ExternalInterruptNumber`, +/// `PriorityNumber`, or `HartIdNumber`. /// /// # Safety /// @@ -422,7 +426,7 @@ core::arch::global_asm!(" /// /// # Example /// -/// ```rust +/// ```rust,ignore,no_run /// use riscv::*; /// /// #[repr(usize)] @@ -433,16 +437,14 @@ core::arch::global_asm!(" /// E3 = 3, /// } /// -/// fn main() { -/// assert_eq!(Exception::E1.number(), 1); -/// assert_eq!(Exception::E3.number(), 3); +/// assert_eq!(Exception::E1.number(), 1); +/// assert_eq!(Exception::E3.number(), 3); /// -/// assert_eq!(Exception::from_number(1), Ok(Exception::E1)); -/// assert_eq!(Exception::from_number(2), Err(2)); -/// assert_eq!(Exception::from_number(3), Ok(Exception::E3)); +/// assert_eq!(Exception::from_number(1), Ok(Exception::E1)); +/// assert_eq!(Exception::from_number(2), Err(2)); +/// assert_eq!(Exception::from_number(3), Ok(Exception::E3)); /// -/// assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); -/// } +/// assert_eq!(Exception::MAX_EXCEPTION_NUMBER, 3); ///``` #[proc_macro_attribute] pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream { diff --git a/riscv-pac/CHANGELOG.md b/riscv-pac/CHANGELOG.md index 9dcfcd6a..606bec23 100644 --- a/riscv-pac/CHANGELOG.md +++ b/riscv-pac/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed + +- Updated the license to `MIT or Apache-2.0` + ## [v0.2.0] - 2024-10-19 ### Added diff --git a/riscv-pac/Cargo.toml b/riscv-pac/Cargo.toml index b5ba86b2..18be9de1 100644 --- a/riscv-pac/Cargo.toml +++ b/riscv-pac/Cargo.toml @@ -9,7 +9,7 @@ categories = ["embedded", "hardware-support", "no-std"] description = "Low level access to RISC-V processors" documentation = "https://docs.rs/riscv-pac" keywords = ["riscv", "register", "peripheral"] -license = "ISC" +license = "MIT OR Apache-2.0" [package.metadata.docs.rs] default-target = "riscv64imac-unknown-none-elf" diff --git a/riscv-peripheral/CHANGELOG.md b/riscv-peripheral/CHANGELOG.md index 244a22af..51a93e53 100644 --- a/riscv-peripheral/CHANGELOG.md +++ b/riscv-peripheral/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed + +- Update license to `MIT or Apache-2.0` + +### Fixed + +- Typo in documentation. + ## [v0.4.0] - 2025-09-08 ### Added diff --git a/riscv-peripheral/Cargo.toml b/riscv-peripheral/Cargo.toml index c0caff29..0a7006cc 100644 --- a/riscv-peripheral/Cargo.toml +++ b/riscv-peripheral/Cargo.toml @@ -9,7 +9,7 @@ categories = ["embedded", "hardware-support", "no-std"] description = "Interfaces for standard RISC-V peripherals" documentation = "https://docs.rs/riscv-peripheral" keywords = ["riscv", "peripheral", "clint", "plic"] -license = "ISC" +license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/riscv-peripheral/src/aclint.rs b/riscv-peripheral/src/aclint.rs index 998edf24..3354431a 100644 --- a/riscv-peripheral/src/aclint.rs +++ b/riscv-peripheral/src/aclint.rs @@ -1,6 +1,6 @@ //! Devices for the Core Local Interruptor (CLINT) and Advanced CLINT (ACLINT) peripherals. //! -//! CLINT pecification: +//! CLINT specification: //! ACLINT Specification: pub mod mswi; diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 847d066b..252a2b7e 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -7,12 +7,32 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Added examples for CI tests using semihosting and UART +- New `no-mhartid` feature to load 0 to `a0` instead of reading `mhartid`. +- New `no-xtvec` feature that removes interrupt stuff. + +### Changed + +- Add features to documentation that were missing and fix `cargo doc` errors. +- Update license to `MIT or Apache-2.0` +- Fix clippy warnings in riscv_rt_macros::strip_type_path +- Bump MSRV to 1.68 for latest syn 2.0 release +- Adapted to new `riscv` version. + +### Fixed + +- Fix v-trap core interrupt so RISCV_RT_BASE_ISA must be defined +- Fix undefined behavior in heap initialization example documentation +- Fix stack allocation algorithm for multi-core targets without M extension + ## [v0.16.0] - 2025-09-08 ### Added - New `post-init` feature to run a Rust `__post_init` function before jumping to `main`. -- New `#[riscv_rt::post_init]` attribute to aid in the definition of the `__post_init` function. +- New `#[riscv_rt::post_init]` attribute to aid in the definition of the `__post_init` function. - Added `.uninit` section to the linker file. Due to its similarities with `.bss`, the linker will place this new section in `REGION_BSS`. - Additional feature `no-xie-xip` to work on chips without the XIE and XIP CSRs (e.g. ESP32-C2, ESP32-C3) @@ -67,7 +87,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - New `device` feature to include `device.x` in `link.x`. This feature is based on the current implementation of `cortex-m-rt`. - New `memory` feature to include `memory.x` in `link.x`. This feature is based - on the current implementation of `cortex-m-rt`. However, in contrast with + on the current implementation of `cortex-m-rt`. However, in contrast with `cortex-m-rt`, including `memory.x` in the linker file is feature gated. The benefits of leaving this optional are backwards compatibility and allowing users to define less typical linker scripts that do not rely on a diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index da9b550c..096784c8 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "riscv-rt" version = "0.16.0" -rust-version = "1.67" +rust-version = "1.68" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] categories = ["embedded", "no-std"] description = "Minimal runtime / startup for RISC-V CPU's" documentation = "https://docs.rs/riscv-rt" keywords = ["riscv", "runtime", "startup"] -license = "ISC" +license = "MIT OR Apache-2.0" edition = "2021" links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked @@ -24,7 +24,7 @@ targets = [ riscv-target-parser = { path = "../riscv-target-parser", version = "0.1.2" } [dependencies] -riscv = { path = "../riscv", version = "0.15.0" } +riscv = { path = "../riscv", version = "0.15.0", features = ["rt"] } riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-rt-macros = { path = "macros", version = "0.6.0" } @@ -32,17 +32,21 @@ defmt = { version = "1.0.1", optional = true } [dev-dependencies] panic-halt = "1.0.0" +riscv-semihosting = { path = "../riscv-semihosting", version = "0.2.1" } +riscv = { path = "../riscv", version = "0.15.0", features = ["critical-section-single-hart"] } [features] pre-init = [] post-init = [] s-mode = ["riscv-rt-macros/s-mode"] single-hart = [] -v-trap = ["riscv-rt-macros/v-trap"] +v-trap = ["riscv-rt-macros/v-trap", "riscv/rt-v-trap"] u-boot = ["riscv-rt-macros/u-boot", "single-hart"] no-interrupts = [] no-exceptions = [] +no-mhartid = ["single-hart"] no-xie-xip = [] +no-xtvec = [] device = [] memory = [] defmt = ["dep:defmt"] diff --git a/riscv-rt/examples/device_virt.x b/riscv-rt/examples/device_virt.x new file mode 100644 index 00000000..ee4e920a --- /dev/null +++ b/riscv-rt/examples/device_virt.x @@ -0,0 +1,11 @@ +MEMORY +{ + RAM : ORIGIN = 0x80000000, LENGTH = 16M +} +REGION_ALIAS("REGION_TEXT", RAM); +REGION_ALIAS("REGION_RODATA", RAM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); +INCLUDE link.x diff --git a/riscv-rt/examples/qemu_semihosting.rs b/riscv-rt/examples/qemu_semihosting.rs new file mode 100644 index 00000000..52d6eb40 --- /dev/null +++ b/riscv-rt/examples/qemu_semihosting.rs @@ -0,0 +1,22 @@ +//! Semihosting example for QEMU +//! +//! This example uses RISC-V semihosting to print output and cleanly exit QEMU. +//! Run with: `qemu-system-riscv32 -machine virt -nographic -semihosting-config enable=on,target=native -bios none -kernel ` + +#![no_std] +#![no_main] + +extern crate panic_halt; + +use riscv_rt::entry; +use riscv_semihosting::{ + debug::{self, EXIT_SUCCESS}, + hprintln, +}; + +#[entry] +fn main() -> ! { + hprintln!("Hello from semihosting!"); + debug::exit(EXIT_SUCCESS); + loop {} +} diff --git a/riscv-rt/examples/qemu_uart.rs b/riscv-rt/examples/qemu_uart.rs new file mode 100644 index 00000000..81971ea7 --- /dev/null +++ b/riscv-rt/examples/qemu_uart.rs @@ -0,0 +1,61 @@ +//! UART example for QEMU virt machine +//! +//! This example demonstrates direct UART output on QEMU's virt machine. +//! It writes to the NS16550-compatible UART at 0x1000_0000. + +#![no_std] +#![no_main] + +extern crate panic_halt; + +use riscv_rt::entry; +use riscv_semihosting::debug::{self, EXIT_SUCCESS}; + +const UART_BASE: usize = 0x1000_0000; +const UART_THR: usize = UART_BASE; +const UART_IER: usize = UART_BASE + 1; +const UART_FCR: usize = UART_BASE + 2; +const UART_LCR: usize = UART_BASE + 3; +const UART_LSR: usize = UART_BASE + 5; +const LCR_DLAB: u8 = 1 << 7; +const LCR_8N1: u8 = 0x03; +const LSR_THRE: u8 = 1 << 5; + +unsafe fn uart_write_reg(off: usize, v: u8) { + (off as *mut u8).write_volatile(v); +} + +unsafe fn uart_read_reg(off: usize) -> u8 { + (off as *const u8).read_volatile() +} + +fn uart_init() { + unsafe { + uart_write_reg(UART_LCR, LCR_DLAB); + uart_write_reg(UART_THR, 0x01); + uart_write_reg(UART_IER, 0x00); + uart_write_reg(UART_LCR, LCR_8N1); + uart_write_reg(UART_FCR, 0x07); + } +} + +fn uart_write_byte(b: u8) { + unsafe { + while (uart_read_reg(UART_LSR) & LSR_THRE) == 0 {} + uart_write_reg(UART_THR, b); + } +} + +fn uart_write_str(s: &str) { + for &b in s.as_bytes() { + uart_write_byte(b); + } +} + +#[entry] +fn main() -> ! { + uart_init(); + uart_write_str("Hello from UART!\n"); + debug::exit(EXIT_SUCCESS); + loop {} +} diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index 53ae6e74..4d899bb9 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -137,7 +137,7 @@ fn strip_type_path(ty: &Type) -> Option { match ty { Type::Ptr(ty) => { let mut ty = ty.clone(); - ty.elem = Box::new(strip_type_path(&ty.elem)?); + *ty.elem = strip_type_path(&ty.elem)?; Some(Type::Ptr(ty)) } Type::Path(ty) => { @@ -783,6 +783,8 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { /// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. /// /// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// This feature relies on the `RISCV_RT_BASE_ISA` environment variable being set to one of +/// `rv32i`, `rv32e`, `rv64i`, or `rv64e`. Otherwise, this will **panic**. /// /// # Example /// @@ -795,7 +797,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { pub fn core_interrupt(args: TokenStream, input: TokenStream) -> TokenStream { let arch = match () { #[cfg(feature = "v-trap")] - () => RiscvArch::try_from_env(), + () => Some(RiscvArch::try_from_env().expect("RISCV_RT_BASE_ISA must be defined")), #[cfg(not(feature = "v-trap"))] () => None, }; diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index b3041bfc..cef84699 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -68,17 +68,26 @@ _abs_start: #[cfg(all(feature = "s-mode", not(feature = "no-xie-xip")))] "csrw sie, 0 csrw sip, 0", - #[cfg(all(not(feature = "s-mode"), not(feature = "no-xie-xip")))] - "csrw mie, 0 - csrw mip, 0", #[cfg(not(feature = "s-mode"))] - "csrr a0, mhartid", // Make sure that the hart ID is in a0 in M-mode + { + #[cfg(not(feature = "no-xie-xip"))] + "csrw mie, 0 + csrw mip, 0", + // Make sure that the hart ID is in a0 in M-mode + #[cfg(not(feature = "no-mhartid"))] + "csrr a0, mhartid", + #[cfg(feature = "no-mhartid")] + "li a0, 0", + } // Set pre-init trap vector - "la t0, _pre_init_trap", - #[cfg(feature = "s-mode")] - "csrw stvec, t0", - #[cfg(not(feature = "s-mode"))] - "csrw mtvec, t0", + #[cfg(not(feature = "no-xtvec"))] + { + "la t0, _pre_init_trap", + #[cfg(feature = "s-mode")] + "csrw stvec, t0", + #[cfg(not(feature = "s-mode"))] + "csrw mtvec, t0", + } // If multi-hart, assert that hart ID is valid #[cfg(not(feature = "single-hart"))] "lui t0, %hi(_max_hart_id) @@ -105,13 +114,13 @@ _abs_start: #[cfg(not(feature = "single-hart"))] { "mv t2, a0 - lui t0, %hi(_hart_stack_size) - add t0, t0, %lo(_hart_stack_size)", + lui t1, %hi(_hart_stack_size) + add t1, t1, %lo(_hart_stack_size)", #[cfg(riscvm)] - "mul t0, t2, t0", + "mul t0, t2, t1", #[cfg(not(riscvm))] - "beqz t2, 2f // skip if hart ID is 0 - mv t1, t0 + "mv t0, x0 + beqz t2, 2f // skip if hart ID is 0 1: add t0, t0, t1 addi t2, t2, -1 diff --git a/riscv-rt/src/interrupts.rs b/riscv-rt/src/interrupts.rs index 44561f86..71b77014 100644 --- a/riscv-rt/src/interrupts.rs +++ b/riscv-rt/src/interrupts.rs @@ -17,10 +17,10 @@ //! you may need to opt out this module. To do so, activate the `no-interrupts` feature of the //! `riscv-rt` crate. -// In vectored mode, we also must provide a vector table #[riscv::pac_enum(unsafe CoreInterruptNumber)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(dead_code)] // otherwise compiler complains about Interrupt not being used enum Interrupt { SupervisorSoft = 1, MachineSoft = 3, diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 4c3b79b5..5766daca 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -156,7 +156,7 @@ //! //! Our application would look like this: //! -//! ```no_run +//! ```ignore,no_run //! // src/main.rs //! #![no_main] //! #![no_std] @@ -269,7 +269,7 @@ //! //! ## Example //! -//! ``` no_run +//! ``` ignore,no_run //! extern crate some_allocator; // e.g., embedded_alloc::LlffHeap //! //! extern "C" { @@ -279,7 +279,7 @@ //! fn main() { //! unsafe { //! let heap_bottom = riscv_rt::heap_start() as usize; -//! let heap_size = &_heap_size as *const u8 as usize; +//! let heap_size = core::ptr::addr_of!(_heap_size) as usize; //! some_allocator::initialize(heap_bottom, heap_size); //! } //! } @@ -357,7 +357,7 @@ //! //! The following example shows how to implement the `_mp_hook` function in assembly. //! -//! ``` no_run +//! ``` ignore,no_run //! core::arch::global_asm!( //! r#".section .init.mp_hook, "ax" //! .global _mp_hook @@ -433,7 +433,7 @@ //! //! This function can be redefined in the following way: //! -//! ``` no_run +//! ``` ignore,no_run //! #[export_name = "ExceptionHandler"] //! fn custom_exception_handler(trap_frame: &riscv_rt::TrapFrame) -> ! { //! // ... @@ -542,7 +542,7 @@ //! //! The following example shows how to implement the `__pre_init` function in assembly. //! -//! ``` no_run +//! ``` ignore,no_run //! core::arch::global_asm!( //! r#".section .init.pre_init, "ax" //! .global __pre_init @@ -565,6 +565,29 @@ //! //! Saves a little code size if there is only one hart on the target. //! +//! ## `no-mhartid` +//! +//! Skips reading `mhartid` and uses 0 instead. Useful for targets that doesn't implement this instruction. +//! Automatically enables `single-hart`. +//! +//! ## `no-xtvec` +//! +//! Skips interrupts setup. +//! +//! ## `no-xie-xip` +//! +//! Skips disabling interrupts (to support chips without XIE/XIP CSRs). +//! +//! ## `no-interrupts` +//! +//! Opts out of the default implementation for `_dispatch_core_interrupt` to support platforms +//! with custom core interrupt sources. +//! +//! ## `no-exceptions` +//! +//! Opts out of the default implementation for `_dispatch_exception` to support platforms +//! with custom exception sources. +//! //! ## `s-mode` //! //! Supervisor mode. While most registers/instructions have variants for both `mcause` and @@ -613,7 +636,7 @@ //! //! ### Example //! -//! ```rust,no_run +//! ```rust,ignore,no_run //! core::arch::global_asm!( //! r#" //! .section .trap.start, "ax" @@ -630,6 +653,20 @@ //! "# //! ); //! ``` +//! +//! ## `device` +//! +//! Automatically includes `device.x` (typically provided by PACs to provide weak aliases to interrupt handlers) +//! in the linker script. +//! +//! ## `memory` +//! +//! Automatically includes [`memory.x`](#memoryx) (typically provided by BSPs) in the linker script. +//! +//! ## `defmt` +//! +//! Implements `defmt::Format` on certain types. +//! //! [attr-entry]: attr.entry.html //! [attr-exception]: attr.exception.html //! [attr-external-interrupt]: attr.external_interrupt.html @@ -729,9 +766,9 @@ pub unsafe extern "Rust" fn setup_interrupts() { let xtvec_val = match () { #[cfg(not(feature = "v-trap"))] - _ => Xtvec::new(_start_trap as usize, TrapMode::Direct), + _ => Xtvec::new(_start_trap as *const () as usize, TrapMode::Direct), #[cfg(feature = "v-trap")] - _ => Xtvec::new(_vector_table as usize, TrapMode::Vectored), + _ => Xtvec::new(_vector_table as *const () as usize, TrapMode::Vectored), }; xtvec::write(xtvec_val); } diff --git a/riscv-target-parser/CHANGELOG.md b/riscv-target-parser/CHANGELOG.md index b805fb6e..b700739d 100644 --- a/riscv-target-parser/CHANGELOG.md +++ b/riscv-target-parser/CHANGELOG.md @@ -5,6 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed + +- Update license to `MIT or Apache 2.0` + +## [v0.1.3] - 2025-09-29 + +### Fixed + +- Skip the 'relax' target feature when parsing extensions + ## [v0.1.2] - 2025-06-10 ### Fixed diff --git a/riscv-target-parser/Cargo.toml b/riscv-target-parser/Cargo.toml index 7612e387..bcc438cc 100644 --- a/riscv-target-parser/Cargo.toml +++ b/riscv-target-parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riscv-target-parser" -version = "0.1.2" +version = "0.1.3" rust-version = "1.61" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] @@ -8,5 +8,5 @@ categories = ["embedded", "no-std"] description = "Parser for RISC-V target specifications" documentation = "https://docs.rs/riscv-target-parser" keywords = ["riscv", "build"] -license = "ISC" +license = "MIT OR Apache-2.0" edition = "2021" diff --git a/riscv-target-parser/src/lib.rs b/riscv-target-parser/src/lib.rs index b557d619..2d39c17a 100644 --- a/riscv-target-parser/src/lib.rs +++ b/riscv-target-parser/src/lib.rs @@ -106,6 +106,12 @@ pub struct RiscvTarget { extensions: Extensions, } +// Returns whether a target feature _might_ be an ISA extension according to a non-exhaustive list +// of known unrelated features flags. +fn is_isa_extension(feature: &str) -> bool { + feature != "relax" +} + impl RiscvTarget { /// Builds a RISC-V target from a target triple and cargo flags. /// This function is expected to be called from a build script. @@ -135,11 +141,15 @@ impl RiscvTarget { }) { if let Some(feature) = target_feature.strip_prefix('+') { - let extension = Extension::try_from(feature)?; - target.extensions.insert(extension); + if is_isa_extension(feature) { + let extension = Extension::try_from(feature)?; + target.extensions.insert(extension); + } } else if let Some(feature) = target_feature.strip_prefix('-') { - let extension = Extension::try_from(feature)?; - target.extensions.remove(&extension); + if is_isa_extension(feature) { + let extension = Extension::try_from(feature)?; + target.extensions.remove(&extension); + } } else { return Err(Error::UnknownTargetFeature(target_feature)); } @@ -246,7 +256,7 @@ mod test { #[test] fn test_parse_target() { let target = "riscv32imac-unknown-none-elf"; - let cargo_flags = "target-feature=+m,-a,+f"; + let cargo_flags = "target-feature=+m,-a,+f,+relax"; let target = super::RiscvTarget::build(target, cargo_flags).unwrap(); let rustc_flags = target.rustc_flags(); assert_eq!(rustc_flags, vec!["riscvi", "riscvm", "riscvf", "riscvc"]); diff --git a/riscv-types/CHANGELOG.md b/riscv-types/CHANGELOG.md new file mode 100644 index 00000000..245c286c --- /dev/null +++ b/riscv-types/CHANGELOG.md @@ -0,0 +1,46 @@ +# 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 + +- Bump MSRV to 1.81 due to `core::error::Error` trait +- Mark `result::Error` as `#[non_exhaustive]` to allow non-breaking new variants +- Update license to `MIT or Apache-2.0` +- Renamed crate to `riscv-types` as per [#351](https://github.com/rust-embedded/riscv/issues/351) + +### Added + +- Implement `core::error::Error` for `result::Error` + +## 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..b06c017f --- /dev/null +++ b/riscv-types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "riscv-types" +version = "0.1.0" +edition = "2021" +rust-version = "1.81" +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 = "MIT OR Apache-2.0" + +[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..89f088d3 --- /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.81 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..fa158529 --- /dev/null +++ b/riscv-types/src/result.rs @@ -0,0 +1,61 @@ +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)] +#[non_exhaustive] +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"), + } + } +} + +impl core::error::Error for Error {} diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 59dc1adb..bb73d637 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Add `vstopi` CSR +- Add `dcsratch0` and `dscratch1` CSRs +- Add new `read-write_csr_as_usize` macro for registers +- Add `dpc` CSR support for RISC-V +- Add Mtopi +- Added DCSR (Debug Control and Status Register) CSR support for the RISC-V +- Add `miselect` CSR +- Improved assembly macro handling in asm.rs +- New `rt` and `rt-v-trap` features to opt-in `riscv-rt`-related code in `riscv::pac_enum` macro. +- Add `mvien` + `mvienh` CSR + +### Changed + +- Fix broken links in register macro doc string. +- Moved macros from `./macros/` to `../riscv-macros/` +- Updated the license to `MIT or Apache-2.0` +- Bump MSRV to 1.68 for latest version of syn 2.0 +- Now, `riscv::pac_enum` macro only includes trap-related code if `rt` or `rt-v-trap` features are enabled. + ## [v0.15.0] - 2025-09-08 ### Added @@ -111,7 +132,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). the CSR - Export `riscv::register::macros` module macros for external use - Add `riscv::register::mcountinhibit` module for `mcountinhibit` CSR -- Add `Mcounteren` in-memory update functions +- Add `Mcounteren` in-memory update functions - Add `Mstatus` vector extension support - Add fallible counterparts to all functions that `panic` - Add `riscv-pac` as a dependency @@ -280,3 +301,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/). [v0.6.0]: https://github.com/rust-embedded/riscv/compare/v0.5.6...v0.6.0 [v0.5.6]: https://github.com/rust-embedded/riscv/compare/v0.5.5...v0.5.6 [v0.5.5]: https://github.com/rust-embedded/riscv/compare/v0.5.4...v0.5.5 +[v0.5.5]: https://github.com/rust-embedded/riscv/compare/v0.5.4...v0.5.5u diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 0cae7f10..f4e87491 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -2,14 +2,14 @@ name = "riscv" version = "0.15.0" edition = "2021" -rust-version = "1.67" +rust-version = "1.68" 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" keywords = ["riscv", "register", "peripheral"] -license = "ISC" +license = "MIT OR Apache-2.0" [package.metadata.docs.rs] all-features = true @@ -23,10 +23,12 @@ targets = [ default = ["riscv-macros"] s-mode = [] critical-section-single-hart = ["critical-section/restore-state-bool"] +rt = ["riscv-macros/rt"] +rt-v-trap = ["rt", "riscv-macros/rt-v-trap"] [dependencies] critical-section = "1.2.0" embedded-hal = "1.0.0" riscv-pac = { path = "../riscv-pac", version = "0.2.0" } -riscv-macros = { path = "macros", version = "0.3.0", optional = true } +riscv-macros = { path = "../riscv-macros", version = "0.3.0", optional = true } paste = "1.0.15" diff --git a/riscv/src/asm.rs b/riscv/src/asm.rs index 5e471ff1..d75c3408 100644 --- a/riscv/src/asm.rs +++ b/riscv/src/asm.rs @@ -20,45 +20,70 @@ macro_rules! instruction { #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] unimplemented!(); } - ); + ) } instruction!( - /// `nop` instruction wrapper + /// `NOP` instruction wrapper /// /// The `NOP` instruction does not change any architecturally visible state, except for - /// advancing the pc and incrementing any applicable performance counters. + /// advancing the PC and incrementing any applicable performance counters. /// /// This function generates a no-operation; it's useful to prevent delay loops from being /// optimized away. - , nop, "nop", options(nomem, nostack)); + , nop, "nop", options(nomem, nostack, preserves_flags)); instruction!( /// `WFI` instruction wrapper /// - /// Provides a hint to the implementation that the current hart can be stalled until an interrupt might need servicing. - /// The WFI instruction is just a hint, and a legal implementation is to implement WFI as a NOP. - , wfi, "wfi", options(nomem, nostack)); + /// Provides a hint to the implementation that the current hart can be stalled until an + /// interrupt might need servicing. The WFI instruction is just a hint, and a legal + /// implementation is to implement WFI as a NOP. + /// + /// # Behavior + /// + /// - May cause the hart to enter a low-power state + /// - Will be interrupted by any enabled interrupt + /// - No guarantee of actual power savings (implementation-dependent) + ,wfi, "wfi", options(nomem, nostack, preserves_flags)); instruction!( /// `EBREAK` instruction wrapper /// - /// Generates a breakpoint exception. - , unsafe ebreak, "ebreak", options(nomem, nostack)); + /// Generates a breakpoint exception for use by debuggers. + /// + /// # Behavior + /// + /// When executed, this instruction causes a breakpoint exception to be raised, + /// which will typically be handled by a debugger or exception handler. + /// + /// # Safety + /// + /// This function is unsafe because it unconditionally generates an exception, + /// which can disrupt normal program flow. Only call this when you intend to + /// trigger a breakpoint. + , unsafe ebreak, "ebreak", options(nomem, nostack, preserves_flags)); instruction!( /// `ECALL` instruction wrapper /// - /// Generates an exception for a service request to the execution environment. - /// When executed in U-mode, S-mode, or M-mode, it generates an environment-call-from-U-mode - /// exception, environment-call-from-S-mode exception, or environment-call-from-M-mode exception, - /// respectively, and performs no other operation. + /// Generates an environment call exception for system services. /// - /// # Note + /// # Behavior /// - /// The ECALL instruction will **NOT** save and restore the stack pointer, as it triggers an exception. - /// The stack pointer must be saved and restored accordingly by the exception handler. - , unsafe ecall, "ecall", options(nomem, nostack)); + /// When executed in different privilege modes: + /// - U-mode: Generates environment-call-from-U-mode exception + /// - S-mode: Generates environment-call-from-S-mode exception + /// - M-mode: Generates environment-call-from-M-mode exception + /// + /// # Safety + /// + /// This function is unsafe because: + /// - It unconditionally generates an exception + /// - The stack pointer is **NOT** automatically saved/restored + /// - The exception handler is responsible for proper context management + /// - Improper use can crash the system + , unsafe ecall, "ecall", options(nomem, nostack, preserves_flags)); instruction!( /// `SFENCE.VMA` instruction wrapper (all address spaces and page table levels) @@ -118,8 +143,8 @@ instruction!( pub fn sfence_vma(asid: usize, addr: usize) { #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] unsafe { - core::arch::asm!("sfence.vma {0}, {1}", in(reg) addr, in(reg) asid, options(nostack)); - }; + core::arch::asm!("sfence.vma {}, {}", in(reg) addr, in(reg) asid, options(nostack)); + } #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] unimplemented!(); } @@ -139,21 +164,19 @@ pub fn sfence_vma(asid: usize, addr: usize) { allow(unused_variables) )] pub fn delay(cycles: u32) { - match () { - #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] - () => { - let real_cyc = 1 + cycles / 2; - unsafe { - core::arch::asm!( + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + { + let real_cyc = 1 + cycles / 2; + unsafe { + core::arch::asm!( "2:", "addi {0}, {0}, -1", "bne {0}, zero, 2b", inout(reg) real_cyc => _, options(nomem, nostack), - ); - } + ); } - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] - () => unimplemented!(), } + #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] + unimplemented!(); } diff --git a/riscv/src/lib.rs b/riscv/src/lib.rs index 838c7d9a..0704f75b 100644 --- a/riscv/src/lib.rs +++ b/riscv/src/lib.rs @@ -31,6 +31,17 @@ //! and may cause functional problems in systems where some interrupts must NOT be disabled //! or critical sections are managed as part of an RTOS. In these cases, you should use //! a target-specific implementation instead, typically provided by a HAL or RTOS crate. +//! +//! ## `rt` +//! +//! This feature enables code related to [`riscv-rt`](https://github.com/rust-embedded/riscv/tree/master/riscv-rt) +//! runtime support in the `riscv::pac_enum` macro. Namely, it enables the generation of +//! trap handler functions and dispatch functions. +//! +//! ## `rt-v-trap` +//! +//! This feature enables code related to vectored trap handling in addition to the `rt` feature. +//! Namely, it enables the generation of a vector table and the corresponding assembly code for core interrupts. #![no_std] #![allow(clippy::missing_safety_doc)] diff --git a/riscv/src/register.rs b/riscv/src/register.rs index cabd1922..8235cb7b 100644 --- a/riscv/src/register.rs +++ b/riscv/src/register.rs @@ -63,6 +63,7 @@ pub mod sepc; pub mod sip; pub mod sscratch; pub mod stval; +pub mod vstopi; // Supervisor Protection and Translation pub mod satp; @@ -89,8 +90,12 @@ pub mod mepc; pub mod mip; pub mod mscratch; pub mod mtinst; +pub mod mtopi; pub mod mtval; pub mod mtval2; +pub mod mvien; +#[cfg(any(test, target_arch = "riscv32"))] +pub mod mvienh; // Machine Protection and Translation mod pmpcfgx; @@ -117,9 +122,16 @@ pub mod mseccfg; #[cfg(any(test, target_arch = "riscv32"))] pub mod mseccfgh; +// Machine indirect access +pub mod miselect; + #[cfg(test)] mod tests; // TODO: Debug/Trace Registers (shared with Debug Mode) // TODO: Debug Mode Registers +pub mod dcsr; +pub mod dpc; +pub mod dscratch0; +pub mod dscratch1; diff --git a/riscv/src/register/dcsr.rs b/riscv/src/register/dcsr.rs new file mode 100644 index 00000000..32354d72 --- /dev/null +++ b/riscv/src/register/dcsr.rs @@ -0,0 +1,194 @@ +//! dcsr register — Debug Control and Status Register (0x7b0) +//! +//! Provides control and status for debug mode, including cause of entry, step control, and privilege level. + +read_write_csr! { + /// Debug Control and Status Register + Dcsr: 0x7b0, + mask: 0xf000_bfdf, +} + +csr_field_enum! { + /// Operating privilege level. + Prv { + default: Machine, + /// User/Application. + User = 0b00, + /// Supervisor. + Supervisor = 0b01, + /// Machine. + Machine = 0b11, + } +} + +csr_field_enum! { + /// Cause for entering debug mode. + Cause { + default: None, + /// No cause. + None = 0, + /// EBREAK instruction. + Ebreak = 1, + /// Trigger module. + Trigger = 2, + /// External halt request. + HaltRequest = 3, + /// Single-step completed. + Step = 4, + /// Reset-halt request. + ResetHaltRequest = 5, + } +} + +read_write_csr_field! { + Dcsr, + /// Previous privilege level when entering debug mode (bits 0..1). + prv, + Prv: [0:1], +} + +read_write_csr_field! { + Dcsr, + /// Single step mode (bit 2) + step: 2, +} + +read_only_csr_field! { + Dcsr, + /// Non-maskable interrupt pending (bit 3) + nmip: 3, +} + +read_write_csr_field! { + Dcsr, + /// Use mstatus.mprv in debug mode (bit 4) + mprven: 4, +} + +read_only_csr_field! { + Dcsr, + /// Cause for entering debug mode (bits 6..8) + cause, + Cause: [6:8], +} + +read_write_csr_field! { + Dcsr, + /// Stop timer increment in debug mode (bit 9) + stoptime: 9, +} + +read_write_csr_field! { + Dcsr, + /// Stop counter increment in debug mode (bit 10) + stopcount: 10, +} + +read_write_csr_field! { + Dcsr, + /// Interrupt enable during single-step (bit 11) + stepie: 11, +} + +read_write_csr_field! { + Dcsr, + /// EBREAK behavior in User mode (bit 12) + ebreaku: 12, +} + +read_write_csr_field! { + Dcsr, + /// EBREAK behavior in Supervisor mode (bit 13) + ebreaks: 13, +} + +read_write_csr_field! { + Dcsr, + /// EBREAK behavior in Machine mode (bit 15) + ebreakm: 15, +} + +read_only_csr_field! { + Dcsr, + /// Debug version (bits 28..31) + xdebugver: [28:31], +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::result::Error; + + #[test] + fn test_dcsr_bitfields() { + let mut dcsr = Dcsr::from_bits(0); + + dcsr.set_step(true); + assert!(dcsr.step()); + dcsr.set_mprven(true); + assert!(dcsr.mprven()); + dcsr.set_stoptime(true); + assert!(dcsr.stoptime()); + dcsr.set_stopcount(true); + assert!(dcsr.stopcount()); + dcsr.set_stepie(true); + assert!(dcsr.stepie()); + dcsr.set_ebreaku(true); + assert!(dcsr.ebreaku()); + dcsr.set_ebreaks(true); + assert!(dcsr.ebreaks()); + dcsr.set_ebreakm(true); + assert!(dcsr.ebreakm()); + + dcsr.set_step(false); + assert!(!dcsr.step()); + dcsr.set_mprven(false); + assert!(!dcsr.mprven()); + dcsr.set_stoptime(false); + assert!(!dcsr.stoptime()); + dcsr.set_stopcount(false); + assert!(!dcsr.stopcount()); + dcsr.set_stepie(false); + assert!(!dcsr.stepie()); + dcsr.set_ebreaku(false); + assert!(!dcsr.ebreaku()); + dcsr.set_ebreaks(false); + assert!(!dcsr.ebreaks()); + dcsr.set_ebreakm(false); + assert!(!dcsr.ebreakm()); + } + + #[test] + fn test_dcsr_enums() { + let mut dcsr = Dcsr::from_bits(0); + + [ + Cause::None, + Cause::Ebreak, + Cause::Trigger, + Cause::HaltRequest, + Cause::Step, + Cause::ResetHaltRequest, + ] + .into_iter() + .enumerate() + .for_each(|(val, variant)| { + dcsr = Dcsr::from_bits((val as usize) << 6); + assert_eq!(dcsr.cause(), variant); + assert_eq!(dcsr.try_cause(), Ok(variant)); + }); + + // invalid variant value 6 + dcsr = Dcsr::from_bits(6 << 6); + assert_eq!(dcsr.try_cause(), Err(Error::InvalidVariant(6))); + } + + #[test] + fn test_dcsr_convenience_methods() { + let mut dcsr = Dcsr::from_bits(0); + + dcsr.set_prv(Prv::Machine); + assert_eq!(dcsr.try_prv().unwrap(), Prv::Machine); + assert_eq!(dcsr.prv(), Prv::Machine); + } +} diff --git a/riscv/src/register/dpc.rs b/riscv/src/register/dpc.rs new file mode 100644 index 00000000..b797be02 --- /dev/null +++ b/riscv/src/register/dpc.rs @@ -0,0 +1,31 @@ +//! dpc register — Debug PC (0x7b1) + +read_write_csr! { + /// Debug PC Register + Dpc: 0x7b1, + mask: !1usize, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dpc_alignment_mask() { + let dpc = Dpc::from_bits(0x1); + assert_eq!(dpc.bits() & 1, 0); + } + + #[test] + fn test_dpc_bits_roundtrip() { + (0..=usize::BITS) + .map(|r| ((1u128 << r) - 1) as usize) + .for_each(|pc| { + // ensure lowest bit is cleared + let exp_pc = pc & !1usize; + let dpc = Dpc::from_bits(pc); + assert_eq!(dpc.bits(), exp_pc); + assert_eq!(Dpc::from_bits(dpc.bits()).bits(), dpc.bits()); + }); + } +} diff --git a/riscv/src/register/dscratch0.rs b/riscv/src/register/dscratch0.rs new file mode 100644 index 00000000..f49687f9 --- /dev/null +++ b/riscv/src/register/dscratch0.rs @@ -0,0 +1,18 @@ +//! dscratch0 + +read_write_csr_as_usize!(0x7b2); + +#[cfg(test)] +mod tests { + use super::*; + use crate::result::Error; + + #[test] + fn test_dscratch0_read_write() { + for i in 0..usize::BITS { + let val = 1usize << i; + assert_eq!(unsafe { try_write(val) }, Err(Error::Unimplemented)); + assert_eq!(try_read(), Err(Error::Unimplemented)); + } + } +} diff --git a/riscv/src/register/dscratch1.rs b/riscv/src/register/dscratch1.rs new file mode 100644 index 00000000..44db9e55 --- /dev/null +++ b/riscv/src/register/dscratch1.rs @@ -0,0 +1,18 @@ +//! dscratch1 + +read_write_csr_as_usize!(0x7b3); + +#[cfg(test)] +mod tests { + use super::*; + use crate::result::Error; + + #[test] + fn test_dscratch1_read_write() { + for i in 0..usize::BITS { + let val = 1usize << i; + assert_eq!(unsafe { try_write(val) }, Err(Error::Unimplemented)); + assert_eq!(try_read(), Err(Error::Unimplemented)); + } + } +} diff --git a/riscv/src/register/macros.rs b/riscv/src/register/macros.rs index b5c9ee8e..e9e9aabc 100644 --- a/riscv/src/register/macros.rs +++ b/riscv/src/register/macros.rs @@ -301,6 +301,30 @@ macro_rules! write_csr_as_usize_rv32 { }; } +/// Convenience macro to provide combined read/write of a CSR as a `usize`. +/// +/// This composes [read_csr_as_usize](crate::read_csr_as_usize) and [write_csr_as_usize](crate::write_csr_as_usize). Use the +/// `safe` form to get safe wrappers instead of unsafe. +#[macro_export] +macro_rules! read_write_csr_as_usize { + ($csr_number:literal) => { + $crate::read_csr_as_usize!($csr_number); + $crate::write_csr_as_usize!($csr_number); + }; + (safe $csr_number:literal) => { + $crate::read_csr_as_usize!($csr_number); + $crate::write_csr_as_usize!(safe $csr_number); + }; + ($csr_number:literal, $($cfg:meta),*) => { + $crate::read_csr_as_usize!($csr_number, $($cfg),*); + $crate::write_csr_as_usize!($csr_number, $($cfg),*); + }; + (safe $csr_number:literal, $($cfg:meta),*) => { + $crate::read_csr_as_usize!($csr_number, $($cfg),*); + $crate::write_csr_as_usize!(safe $csr_number, $($cfg),*); + }; +} + /// Convenience macro around the `csrrs` assembly instruction to set the CSR register. /// /// This macro is intended for use with the [set_csr](crate::set_csr) or [set_clear_csr](crate::set_clear_csr) macros. @@ -1070,3 +1094,15 @@ macro_rules! test_csr_field { } }}; } + +#[cfg(test)] +#[macro_export] +macro_rules! test_ro_csr_field { + ($reg:ident, $field:ident: [$start:expr, $end:expr], $expected:expr) => {{ + let bits = $reg.bits(); + let exp_val = $crate::bits::bf_extract(bits, $start, $end - $start + 1); + let val = $reg.$field(); + assert_eq!(val, exp_val); + assert_eq!(val, $expected); + }}; +} diff --git a/riscv/src/register/miselect.rs b/riscv/src/register/miselect.rs new file mode 100644 index 00000000..50aa624f --- /dev/null +++ b/riscv/src/register/miselect.rs @@ -0,0 +1,64 @@ +//! `miselect` register. + +const MASK: usize = usize::MAX; + +read_write_csr! { + /// `miselect` register. + Miselect: 0x350, + mask: MASK, +} + +#[cfg(target_arch = "riscv32")] +read_write_csr_field! { + Miselect, + /// Returns whether `miselect` is for custom use of indirect CSRs. + is_custom: 31, +} + +#[cfg(not(target_arch = "riscv32"))] +read_write_csr_field! { + Miselect, + /// Returns whether `miselect` is for custom use of indirect CSRs. + is_custom: 63, +} + +#[cfg(target_arch = "riscv32")] +read_write_csr_field! { + Miselect, + /// Gets the value stored in the `miselect` CSR. + /// + /// # Note + /// + /// The semantics of the value depend on the extension for the referenced CSR, + /// and the relevant `mireg*` value. + value: [0:30], +} + +#[cfg(not(target_arch = "riscv32"))] +read_write_csr_field! { + Miselect, + /// Gets the value stored in the `miselect` CSR. + /// + /// # Note + /// + /// The semantics of the value depend on the extension for the referenced CSR, + /// and the relevant `mireg*` value. + value: [0:62], +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + (0..=usize::BITS) + .map(|r| ((1u128 << r) - 1) as usize) + .for_each(|bits| { + let mut miselect = Miselect::from_bits(bits); + + test_csr_field!(miselect, is_custom); + test_csr_field!(miselect, value: [0, usize::BITS - 2], 0); + }); + } +} diff --git a/riscv/src/register/mtopi.rs b/riscv/src/register/mtopi.rs new file mode 100644 index 00000000..859b17c2 --- /dev/null +++ b/riscv/src/register/mtopi.rs @@ -0,0 +1,90 @@ +//! mtopi register — Machine Top Priority Interrupt (0x7C0) +//! +//! Provides information about the highest-priority pending interrupt when AIA (Advanced Interrupt Architecture) is supported. +//! This CSR is part of the RISC-V Advanced Interrupt Architecture extension and allows software to quickly +//! identify the most important pending interrupt without scanning through multiple interrupt pending registers. +//! +//! # Usage +//! +//! ```no_run +//! use riscv::register::mtopi; +//! +//! // Read the machine top priority interrupt register +//! let mtopi_val = mtopi::read(); +//! +//! if mtopi_val.is_interrupt_pending() { +//! let interrupt_id = mtopi_val.iid(); +//! let priority = mtopi_val.iprio(); +//! println!("Highest priority interrupt: ID={}, Priority={}", interrupt_id, priority); +//! } else { +//! println!("No interrupts pending"); +//! } +//! ``` + +read_only_csr! { + /// Machine Top Priority Interrupt Register + Mtopi: 0x7C0, + mask: 0x0FFF_00FF, +} + +read_only_csr_field! { + Mtopi, + /// Interrupt ID (bits 16..27) + /// + /// Identifies the specific interrupt source. A value of 0 indicates no interrupt is pending. + /// Non-zero values correspond to specific interrupt sources as defined by the interrupt controller. + iid: [16:27], +} + +read_only_csr_field! { + Mtopi, + /// Interrupt Priority ID (bits 0..7) + /// + /// Represents the priority level of the pending interrupt. + /// Lower numerical values indicate higher priority interrupts. + iprio: [0:7], +} + +impl Mtopi { + /// Returns true if there is a valid interrupt pending + /// + /// When this returns true, both `interrupt_id()` and `priority()` will return meaningful values. + #[inline] + pub fn is_interrupt_pending(&self) -> bool { + self.iid() != 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mtopi_fields() { + let mtopi = Mtopi::from_bits(0); + test_ro_csr_field!(mtopi, iid: [16, 27], 0x0); + test_ro_csr_field!(mtopi, iprio: [0, 7], 0x0); + + let mtopi = Mtopi::from_bits((0xB << 16) | 5); + test_ro_csr_field!(mtopi, iid: [16, 27], 0xB); + test_ro_csr_field!(mtopi, iprio: [0, 7], 0x5); + + let mtopi = Mtopi::from_bits((0xFFF << 16) | 0xFF); + test_ro_csr_field!(mtopi, iid: [16, 27], 0xFFF); + test_ro_csr_field!(mtopi, iprio: [0, 7], 0xFF); + + let mtopi = Mtopi::from_bits(1 << 16); + test_ro_csr_field!(mtopi, iid: [16, 27], 0x1); + test_ro_csr_field!(mtopi, iprio: [0, 7], 0x0); + + let mtopi = Mtopi::from_bits(1); + test_ro_csr_field!(mtopi, iid: [16, 27], 0x0); + test_ro_csr_field!(mtopi, iprio: [0, 7], 0x1); + } + + #[test] + fn test_mtopi_bitmask() { + let mtopi = Mtopi::from_bits(usize::MAX); + assert_eq!(mtopi.bits(), 0x0FFF_00FFusize); + } +} diff --git a/riscv/src/register/mvien.rs b/riscv/src/register/mvien.rs new file mode 100644 index 00000000..af162063 --- /dev/null +++ b/riscv/src/register/mvien.rs @@ -0,0 +1,157 @@ +//! mvien register + +use crate::bits::{bf_extract, bf_insert}; +use riscv_pac::result::{Error, Result}; +use riscv_pac::InterruptNumber; + +#[cfg(target_arch = "riscv32")] +const MASK: usize = 0xffff_e222; +#[cfg(not(target_arch = "riscv32"))] +const MASK: usize = 0xffff_ffff_ffff_e222; + +read_write_csr! { + /// `mvien` register + Mvien: 0x308, + mask: MASK, +} + +read_write_csr_field! { + Mvien, + /// Alias of `mie.SSIE` + ssoft: 1, +} + +read_write_csr_field! { + Mvien, + /// Alias of `mie.STIE` + stimer: 5, +} + +read_write_csr_field! { + Mvien, + /// Alias of `mie.SEIE` + sext: 9, +} + +impl Mvien { + /// Represents the minimum interrupt of the unlabelled virtual interrupt range. + pub const MIN_INTERRUPT: usize = 13; + /// Represents the maximum interrupt of the unlabelled virtual interrupt range. + #[cfg(target_arch = "riscv32")] + pub const MAX_INTERRUPT: usize = 31; + /// Represents the maximum interrupt of the unlabelled virtual interrupt range. + #[cfg(not(target_arch = "riscv32"))] + pub const MAX_INTERRUPT: usize = 63; + + /// Gets whether the interrupt number is a valid virtual interrupt. + #[inline] + pub const fn is_valid_interrupt(int: usize) -> bool { + matches!(int, 1 | 5 | 9 | Self::MIN_INTERRUPT..=Self::MAX_INTERRUPT) + } + + /// Check if a specific core interrupt source is enabled. + /// + /// Returns `Error` if the interrupt number is invalid. + #[inline] + pub fn is_enabled(&self, interrupt: I) -> bool { + let n = interrupt.number(); + + Self::is_valid_interrupt(n) && bf_extract(self.bits, n, 1) != 0 + } + + /// Enable a specific core interrupt source. + /// + /// Returns `Error` if the interrupt number is invalid. + #[inline] + pub fn enable(&mut self, interrupt: I) -> Result<()> { + let n = interrupt.number(); + + if Self::is_valid_interrupt(n) { + self.bits = bf_insert(self.bits, n, 1, 1); + Ok(()) + } else { + Err(Error::InvalidVariant(n)) + } + } + + /// Disable a specific core interrupt source. + /// + /// Returns `Error` if the interrupt number is invalid. + #[inline] + pub fn disable(&mut self, interrupt: I) -> Result<()> { + let n = interrupt.number(); + + if Self::is_valid_interrupt(n) { + self.bits = bf_insert(self.bits, n, 1, 0); + Ok(()) + } else { + Err(Error::InvalidVariant(n)) + } + } +} + +set!(0x308); +clear!(0x308); + +set_clear_csr!( + /// Supervisor Software Interrupt Enable + , set_ssoft, clear_ssoft, 1 << 1); +set_clear_csr!( + /// Supervisor Timer Interrupt Enable + , set_stimer, clear_stimer, 1 << 5); +set_clear_csr!( + /// Supervisor External Interrupt Enable + , set_sext, clear_sext, 1 << 9); + +read_composite_csr!(super::mvienh::read().bits(), read().bits()); + +#[cfg(test)] +mod tests { + use super::*; + + /// Represents a custom set of virtual interrupts. + /// + /// NOTE: a real implementation may want to enumerate the valid virtual interrupt variants. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct VirtualInterrupt(usize); + + /// SAFETY: `VirtualInterrupt` represents the virtual RISC-V interrupts + unsafe impl InterruptNumber for VirtualInterrupt { + const MAX_INTERRUPT_NUMBER: usize = Mvien::MAX_INTERRUPT; + + #[inline] + fn number(self) -> usize { + self.0 + } + + #[inline] + fn from_number(value: usize) -> Result { + if Mvien::is_valid_interrupt(value) { + Ok(Self(value)) + } else { + Err(Error::InvalidVariant(value)) + } + } + } + + #[test] + fn test_mvien() { + let mut m = Mvien::from_bits(0); + + test_csr_field!(m, ssoft); + test_csr_field!(m, stimer); + test_csr_field!(m, sext); + + (0..=VirtualInterrupt::MAX_INTERRUPT_NUMBER) + .filter_map(|n| VirtualInterrupt::from_number(n).ok()) + .for_each(|int| { + assert!(!m.is_enabled(int)); + + assert!(m.enable(int).is_ok()); + assert!(m.is_enabled(int)); + + assert!(m.disable(int).is_ok()); + assert!(!m.is_enabled(int)); + }); + } +} diff --git a/riscv/src/register/mvienh.rs b/riscv/src/register/mvienh.rs new file mode 100644 index 00000000..9ec5438f --- /dev/null +++ b/riscv/src/register/mvienh.rs @@ -0,0 +1,118 @@ +//! mvienh register + +use crate::bits::{bf_extract, bf_insert}; +use riscv_pac::result::{Error, Result}; +use riscv_pac::InterruptNumber; + +read_write_csr! { + /// `mvienh` register + Mvienh: 0x318, + mask: 0xffff_ffff, +} + +set!(0x318); +clear!(0x318); + +impl Mvienh { + /// Represents the value to shift interrupt numbers to their relative value. + pub const INTERRUPT_SHIFT: usize = 32; + /// Represents the minimum interrupt of the unlabelled virtual interrupt range. + pub const MIN_INTERRUPT: usize = 32; + /// Represents the maximum interrupt of the unlabelled virtual interrupt range. + pub const MAX_INTERRUPT: usize = 63; + + /// Gets whether the interrupt number is a valid virtual interrupt. + #[inline] + pub const fn is_valid_interrupt(int: usize) -> bool { + matches!(int, Self::MIN_INTERRUPT..=Self::MAX_INTERRUPT) + } + + /// Shifts the high-order interrupt number bits down to their relative value. + #[inline] + pub const fn shift_interrupt(int: usize) -> usize { + int.saturating_sub(Self::INTERRUPT_SHIFT) + } + + /// Check if a specific core interrupt source is enabled. + /// + /// Returns `Error` if the interrupt number is invalid. + #[inline] + pub fn is_enabled(&self, interrupt: I) -> bool { + let n = interrupt.number(); + + Self::is_valid_interrupt(n) && bf_extract(self.bits, Self::shift_interrupt(n), 1) != 0 + } + + /// Enable a specific core interrupt source. + #[inline] + pub fn enable(&mut self, interrupt: I) -> Result<()> { + let n = interrupt.number(); + + if Self::is_valid_interrupt(n) { + self.bits = bf_insert(self.bits, Self::shift_interrupt(n), 1, 1); + Ok(()) + } else { + Err(Error::InvalidVariant(n)) + } + } + + /// Disable a specific core interrupt source. + #[inline] + pub fn disable(&mut self, interrupt: I) -> Result<()> { + let n = interrupt.number(); + + if Self::is_valid_interrupt(n) { + self.bits = bf_insert(self.bits, Self::shift_interrupt(n), 1, 0); + Ok(()) + } else { + Err(Error::InvalidVariant(n)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Represents a custom set of virtual interrupts. + /// + /// NOTE: a real implementation may want to enumerate the valid virtual interrupt variants. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct VirtualInterrupt(usize); + + /// SAFETY: `VirtualInterrupt` represents the virtual RISC-V interrupts + unsafe impl InterruptNumber for VirtualInterrupt { + const MAX_INTERRUPT_NUMBER: usize = Mvienh::MAX_INTERRUPT; + + #[inline] + fn number(self) -> usize { + self.0 + } + + #[inline] + fn from_number(value: usize) -> Result { + if Mvienh::is_valid_interrupt(value) { + Ok(Self(value)) + } else { + Err(Error::InvalidVariant(value)) + } + } + } + + #[test] + fn test_mvienh() { + let mut m = Mvienh::from_bits(0); + + (Mvienh::MIN_INTERRUPT..=Mvienh::MAX_INTERRUPT) + .filter_map(|n| VirtualInterrupt::from_number(n).ok()) + .for_each(|int| { + assert!(!m.is_enabled(int)); + + assert!(m.enable(int).is_ok()); + assert!(m.is_enabled(int)); + + assert!(m.disable(int).is_ok()); + assert!(!m.is_enabled(int)); + }); + } +} diff --git a/riscv/src/register/vstopi.rs b/riscv/src/register/vstopi.rs new file mode 100644 index 00000000..129cc6d8 --- /dev/null +++ b/riscv/src/register/vstopi.rs @@ -0,0 +1,69 @@ +//! vstopi register — Virtual Supervisor Top Priority Interrupt (0xEB0) + +read_only_csr! { + /// Virtual Supervisor Top Priority Interrupt Register + Vstopi: 0xEB0, + mask: 0x0FFF_00FF, +} + +read_only_csr_field! { + Vstopi, + /// Interrupt ID (bits 16..27) + /// + /// Identifies the specific interrupt source. A value of 0 indicates no interrupt is pending. + /// Non-zero values correspond to specific interrupt sources as defined by the interrupt controller. + iid: [16:27], +} + +read_only_csr_field! { + Vstopi, + /// Interrupt Priority ID (bits 0..7) + /// + /// Represents the priority level of the pending interrupt. + /// Lower numerical values indicate higher priority interrupts. + iprio: [0:7], +} + +impl Vstopi { + /// Returns true if there is a valid interrupt pending + /// + /// When this returns true, both `interrupt_id()` and `priority()` will return meaningful values. + #[inline] + pub fn is_interrupt_pending(&self) -> bool { + self.iid() != 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vstopi_fields() { + let vstopi = Vstopi::from_bits(0); + test_ro_csr_field!(vstopi, iid: [16, 27], 0x0); + test_ro_csr_field!(vstopi, iprio: [0, 7], 0x0); + + let vstopi = Vstopi::from_bits((0xB << 16) | 5); + test_ro_csr_field!(vstopi, iid: [16, 27], 0xB); + test_ro_csr_field!(vstopi, iprio: [0, 7], 0x5); + + let vstopi = Vstopi::from_bits((0xFFF << 16) | 0xFF); + test_ro_csr_field!(vstopi, iid: [16, 27], 0xFFF); + test_ro_csr_field!(vstopi, iprio: [0, 7], 0xFF); + + let vstopi = Vstopi::from_bits(1 << 16); + test_ro_csr_field!(vstopi, iid: [16, 27], 0x1); + test_ro_csr_field!(vstopi, iprio: [0, 7], 0x0); + + let vstopi = Vstopi::from_bits(1); + test_ro_csr_field!(vstopi, iid: [16, 27], 0x0); + test_ro_csr_field!(vstopi, iprio: [0, 7], 0x1); + } + + #[test] + fn test_vstopi_bitmask() { + let vstopi = Vstopi::from_bits(usize::MAX); + assert_eq!(vstopi.bits(), 0x0FFF_00FFusize); + } +} diff --git a/typos.toml b/typos.toml index a5674cea..c05ff9cf 100644 --- a/typos.toml +++ b/typos.toml @@ -1,2 +1,3 @@ [default] -extend-ignore-re = ["[Ss][Ii][Ee]", "[Ss][Xx][Ll]", "[.]?useed[.,:]?"] +extend-ignore-re = ["[Ss][Ii][Ee]", "[Ss][Xx][Ll]", "[.]?useed[.,:]?", "[Ss][Tt][Ii][Pp]"] +extend-ignore-words-re = ["[Pp]endings", "PENDINGS", "THR", "THRE"] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..13979c34 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..876dae51 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,163 @@ +use anyhow::{bail, Context}; +use std::{ + fs, + path::PathBuf, + process::{Command, Stdio}, +}; + +fn find_golden_file(target: &str, example: &str) -> Option { + let target_specific: PathBuf = ["ci", "expected", target, &format!("{}.run", example)] + .iter() + .collect(); + if target_specific.exists() { + return Some(target_specific); + } + + let generic: PathBuf = ["ci", "expected", &format!("{}.run", example)] + .iter() + .collect(); + if generic.exists() { + return Some(generic); + } + + None +} + +fn main() -> anyhow::Result<()> { + let mut args = std::env::args().skip(1).collect::>(); + if args.is_empty() || args[0] != "qemu" { + bail!("usage: cargo run -p xtask -- qemu --target --example "); + } + args.remove(0); + let mut target = None; + let mut example = None; + let mut features: Option = None; + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "--target" => { + target = Some(args.get(i + 1).context("missing target")?.clone()); + i += 2; + } + "--example" => { + example = Some(args.get(i + 1).context("missing example")?.clone()); + i += 2; + } + "--features" => { + features = Some(args.get(i + 1).context("missing features")?.clone()); + i += 2; + } + _ => { + bail!("unknown arg {}", args[i]); + } + } + } + let target = target.context("--target required")?; + let example = example.context("--example required")?; + let mut rustflags = "-C link-arg=-Triscv-rt/examples/device_virt.x".to_string(); + if let Some(f) = &features { + if f.contains("s-mode") { + rustflags = "-C link-arg=-Triscv-rt/examples/device_virt_s.x".into(); + } + } + + let mut cmd = Command::new("cargo"); + cmd.env("RUSTFLAGS", rustflags).args([ + "build", + "--package", + "riscv-rt", + "--release", + "--target", + &target, + "--example", + &example, + ]); + cmd.apply_features(features.as_deref()); + let status = cmd.status()?; + if !status.success() { + bail!("build failed"); + } + + let qemu = if target.starts_with("riscv32") { + "qemu-system-riscv32" + } else { + "qemu-system-riscv64" + }; + let mut qemu_args = vec![ + "-machine", + "virt", + "-nographic", + "-serial", + "stdio", + "-monitor", + "none", + "-semihosting-config", + "enable=on,target=native", + ]; + if !features.as_deref().unwrap_or("").contains("s-mode") { + qemu_args.push("-bios"); + qemu_args.push("none"); + } + let kernel_path = format!("target/{}/release/examples/{}", target, example); + let child = Command::new(qemu) + .args(&qemu_args) + .arg("-kernel") + .arg(&kernel_path) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("running qemu")?; + let output = child.wait_with_output()?; + let raw_stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + let stdout = raw_stdout + .lines() + .filter(|l| !l.starts_with("QEMU ") && !l.contains("monitor")) + .collect::>() + .join("\n"); + let stdout = if stdout.is_empty() { + String::new() + } else { + format!("{}\n", stdout.trim()) + }; + + let expected_path = match find_golden_file(&target, &example) { + Some(p) => p, + None => { + let target_path: PathBuf = ["ci", "expected", &target, &format!("{}.run", example)] + .iter() + .collect(); + let generic_path: PathBuf = ["ci", "expected", &format!("{}.run", example)] + .iter() + .collect(); + bail!( + "golden file not found. Expected one of:\n - {}\n - {}", + target_path.display(), + generic_path.display() + ); + } + }; + let expected = fs::read_to_string(&expected_path)?; + if expected != stdout { + bail!( + "output mismatch\nexpected: {}\nactual: {}", + expected, + stdout + ); + } + if !stdout.is_empty() { + println!("{}", stdout.trim_end()); + } + Ok(()) +} + +trait CmdExt { + fn apply_features(&mut self, f: Option<&str>) -> &mut Self; +} +impl CmdExt for std::process::Command { + fn apply_features(&mut self, f: Option<&str>) -> &mut Self { + if let Some(feat) = f { + self.arg("--features").arg(feat); + } + self + } +}