diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..758fde0 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.armv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -kernel" + +[target.armv7r-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -kernel" + +[target.armv7r-none-eabi] +# change to '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise +# qemu-system-arm will lock up +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -kernel" diff --git a/.github/bors.toml b/.github/bors.toml deleted file mode 100644 index 61a45db..0000000 --- a/.github/bors.toml +++ /dev/null @@ -1,3 +0,0 @@ -delete_merged_branches = true -required_approvals = 1 -status = ["continuous-integration/travis-ci/push"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5290bee --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,229 @@ +on: + push: + branches: [ main ] + pull_request: + +name: Build + +jobs: + # Build the workspace for a target architecture + build: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, 1.82] + target: + - armebv7r-none-eabi + - armebv7r-none-eabihf + - armv7r-none-eabi + - armv7r-none-eabihf + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + rustup target add ${{ matrix.target }} + - name: Build + run: | + cargo build --target ${{ matrix.target }} + + # Build the host tools + build-host: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, 1.82] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + - name: Build + run: | + cd arm-targets + cargo build + + # Build the workspace for the target architecture but using nightly to compile libcore + build-tier3: + runs-on: ubuntu-24.04 + strategy: + matrix: + target: + - armebv7r-none-eabi + - armebv7r-none-eabihf + - armv7r-none-eabi + - armv7r-none-eabihf + - armv8r-none-eabihf + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install nightly + rustup default nightly + rustup component add rust-src --toolchain nightly + - name: Build + run: | + cargo build --target ${{ matrix.target }} -Zbuild-std=core + + # Gather all the above build jobs together for the purposes of getting an overall pass-fail + build-all: + runs-on: ubuntu-24.04 + needs: [build, build-tier3, build-host] + steps: + - run: /bin/true + + # Build the docs for the workspace + docs: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, 1.82] + target: + - armebv7r-none-eabi + - armebv7r-none-eabihf + - armv7r-none-eabi + - armv7r-none-eabihf + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + rustup target add ${{ matrix.target }} + - name: Build docs + run: | + cargo doc --target ${{ matrix.target }} + + # Build the docs for the host tools + docs-host: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, 1.82] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + - name: Build docs + run: | + cd arm-targets + cargo doc + + # Gather all the above doc jobs together for the purposes of getting an overall pass-fail + docs-all: + runs-on: ubuntu-24.04 + needs: [docs, docs-host] + steps: + - run: /bin/true + + # Format the workspace + fmt: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install stable + rustup default stable + - name: Format + run: | + cargo fmt --check + + # Format the host tools + fmt-host: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install stable + rustup default stable + - name: Format + run: | + cd arm-targets + cargo fmt --check + + # Gather all the above fmt jobs together for the purposes of getting an overall pass-fail + fmt-all: + runs-on: ubuntu-24.04 + needs: [fmt, fmt-host] + steps: + - run: /bin/true + + # Run clippy on the workpace + clippy: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, 1.82] + target: + - armebv7r-none-eabi + - armebv7r-none-eabihf + - armv7r-none-eabi + - armv7r-none-eabihf + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + rustup target add ${{ matrix.target }} + rustup component add clippy + - name: Clippy + run: | + cargo clippy --target ${{ matrix.target }} + + # Run clippy on the host tools + clippy-host: + runs-on: ubuntu-24.04 + strategy: + matrix: + rust: [stable, 1.82] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install rust + run: | + rustup install ${{ matrix.rust }} + rustup default ${{ matrix.rust }} + rustup component add clippy + - name: Clippy + run: | + cd arm-targets + cargo clippy + + # Gather all the above clippy jobs together for the purposes of getting an overall pass-fail + clippy-all: + runs-on: ubuntu-24.04 + needs: [clippy, clippy-host] + steps: + - run: /bin/true + + # Run some programs in QEMU + test: + runs-on: ubuntu-24.04 + needs: [build-all] + steps: + - run: sudo apt-get -y update && sudo apt-get -y install qemu-system-arm + - name: Checkout + uses: actions/checkout@v4 + - run: ./tests.sh + + # Gather all the above xxx-all jobs together for the purposes of getting an overall pass-fail + all: + runs-on: ubuntu-24.04 + needs: [docs-all, build-all, fmt-all, test] # not gating on clippy-all + steps: + - run: /bin/true diff --git a/.gitignore b/.gitignore index 2691a8f..a9d37c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ -**/*.rs.bk -.#* -/target +target Cargo.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fecb77e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,70 +0,0 @@ -language: rust - -matrix: - include: - - env: TARGET=x86_64-unknown-linux-gnu - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: stable - - - env: TARGET=armebv7r-none-eabi - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: stable - - - env: TARGET=armebv7r-none-eabihf - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: stable - - - env: TARGET=armv7r-none-eabi - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: stable - - - env: TARGET=armv7r-none-eabihf - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: stable - - # MSRV - - env: TARGET=x86_64-unknown-linux-gnu - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: 1.31.0 - - - env: TARGET=armebv7r-none-eabi - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: 1.31.0 - - - env: TARGET=armebv7r-none-eabihf - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: 1.31.0 - - - env: TARGET=armv7r-none-eabi - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: 1.31.0 - - - env: TARGET=armv7r-none-eabihf - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - rust: 1.31.0 - -before_install: set -e - -install: - - bash ci/install.sh - - export PATH="$PATH:$PWD/gcc/bin" - -script: - - bash ci/script.sh - -after_script: set +e - -cache: cargo - -before_cache: - - chmod -R a+r $HOME/.cargo; - -branches: - only: - - master - - staging - - trying - -notifications: - email: - on_success: never diff --git a/Cargo.toml b/Cargo.toml index cc1e4d5..7365fb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ -[package] -authors = ["Cortex-R team "] -edition = "2018" -name = "cortex-r" -version = "0.1.0-alpha" - -[dependencies] +[workspace] +exclude = [ + "arm-targets", +] +members = [ + "cortex-r", + "cortex-r-examples", + "cortex-r-rt", +] +resolver = "2" diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 16fe87b..1b5ec8b 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 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 b/LICENSE-MIT index 16815d8..4ee62c1 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,25 +1,19 @@ -Copyright (c) 2019 The Cortex-R team +Copyright (c) 2025 Ferrous Systems -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: +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 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. +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/README.md b/README.md index 0ab95fd..1d66b38 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,34 @@ -# `cortex-r` +# Rust on Arm Cortex-R -> Low level access to Cortex-R processors +This repository contains support libraries for Rust on Arm Cortex-R. -This project is developed and maintained by the [Cortex-R team][team]. +These libraries have been written by Ferrous Systems, and are based on the +[`cortex-m` libraries] from the [Rust Embedded Devices Working Group]. -## Minimum Supported Rust Version (MSRV) +[`cortex-m` libraries]: https://github.com/rust-embedded/cortex-m +[Rust Embedded Devices Working Group]: https://github.com/rust-embedded -This crate is guaranteed to compile on stable Rust 1.31.0 and up. It *might* -compile with older versions but that may change in any new patch release. +There are currently three libraries here: -## License +* [cortex-r](./cortex-r/) - support library for Cortex-R CPUs (like [cortex-m]) +* [cortex-r-rt](./cortex-r-rt/) - run-time library for Cortex-R CPUs (like [cortex-m-rt]) +* [arm-targets](./arm-targets/) - a helper library for your build.rs that sets various `--cfg` flags according to the current target -Licensed under either of +There are also example programs for QEMU in the [cortex-r-examples](./cortex-r-examples/) folder. -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +[cortex-m]: https://crates.io/crates/cortex-m +[cortex-m-rt]: https://crates.io/crates/cortex-m-rt -at your option. +## Licence -### Contribution +* Copyright (c) Ferrous Systems, 2025 +* Copyright (c) The Cortex-M Team -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the -work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any -additional terms or conditions. - -## Code of Conduct +Licensed under either [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE) at +your option. -Contribution to this crate is organized under the terms of the [Rust Code of -Conduct][CoC], the maintainer of this crate, the [Cortex-R team][team], promises -to intervene to uphold that code of conduct. +## Contribution -[CoC]: CODE_OF_CONDUCT.md -[team]: https://github.com/rust-embedded/wg#the-cortex-r-team +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/arm-targets/.gitignore b/arm-targets/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/arm-targets/.gitignore @@ -0,0 +1 @@ +target diff --git a/arm-targets/Cargo.toml b/arm-targets/Cargo.toml new file mode 100644 index 0000000..94c1ed9 --- /dev/null +++ b/arm-targets/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["Jonathan Pallant ", "The Cortex-R Team "] +description = "Compile-time feature detection for Arm processors" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "arm-targets" +readme = "README.md" +repository = "https://github.com/rust-embedded/cortex-r.git" +rust-version = "1.82" +version = "0.1.0" + +[dependencies] diff --git a/arm-targets/README.md b/arm-targets/README.md new file mode 100644 index 0000000..104bd17 --- /dev/null +++ b/arm-targets/README.md @@ -0,0 +1,46 @@ +# Compile-time Support for Arm Targets + +This crate looks at your build target (using the `$TARGET` environment variable +that `cargo` sets) and provides a selection of `--cfg` values to `rustc` that +might be useful. + +Add to your build dependencies and make a `build.rs` file like this: + +```rust +fn main() { + arm_targets::process(); +} +``` + +Cargo will be given configuration like this: + +```text +cargo:rustc-cfg=arm_architecture="v7-r" +cargo:rustc-check-cfg=cfg(arm_architecture, values("v6-m", "v7-m", "v7e-m", "v8-m.base", "v8-m.main", "v7-r", "v8-r", "v7-a", "v8-a")) +cargo:rustc-cfg=arm_isa="A32" +cargo:rustc-check-cfg=cfg(arm_isa, values("A64", "A32", "T32")) +``` + +This allows you to write Rust code in your firmware like: + +```rust +#[cfg(any(arm_architecture = "v7-r", arm_architecture = "v8-r"))] +``` + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.82.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## Licence + +Copyright (c) Ferrous Systems, 2025 + +Licensed under either [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/arm-targets/src/lib.rs b/arm-targets/src/lib.rs new file mode 100644 index 0000000..9b0c652 --- /dev/null +++ b/arm-targets/src/lib.rs @@ -0,0 +1,229 @@ +//! Useful helpers when building Arm code +//! +//! Hopefully Rust will stabilise these kinds of target features, and this won't +//! be required. + +/// Process the ${TARGET} environment variable, and emit cargo configuration to +/// standard out. +pub fn process() { + let target = std::env::var("TARGET").expect("build script TARGET variable"); + process_target(&target); +} + +/// Process a given target string, and emit cargo configuration to standard out. +pub fn process_target(target: &str) { + if let Some(isa) = Isa::get(target) { + println!(r#"cargo:rustc-cfg=arm_isa="{}""#, isa); + } + println!( + r#"cargo:rustc-check-cfg=cfg(arm_isa, values({}))"#, + Isa::values() + ); + + if let Some(arch) = Arch::get(target) { + println!(r#"cargo:rustc-cfg=arm_architecture="{}""#, arch); + } + println!( + r#"cargo:rustc-check-cfg=cfg(arm_architecture, values({}))"#, + Arch::values() + ); + + if let Some(profile) = Profile::get(target) { + println!(r#"cargo:rustc-cfg=arm_profile="{}""#, profile); + } + println!( + r#"cargo:rustc-check-cfg=cfg(arm_profile, values({}))"#, + Profile::values() + ); +} + +/// The Arm Instruction Set +pub enum Isa { + /// A64 instructions are executed by Arm processors in Aarch64 mode + A64, + /// A32 instructions are executed by Arm processors in Aarch32 Arm mode + A32, + /// T32 instructions are executed by Arm processors in Aarch32 Thumb mode + T32, +} + +impl Isa { + /// Decode a target string + pub fn get(target: &str) -> Option { + let arch = Arch::get(target)?; + Some(match arch { + Arch::Armv6M => Isa::T32, + Arch::Armv7M => Isa::T32, + Arch::Armv7EM => Isa::T32, + Arch::Armv8MBase => Isa::T32, + Arch::Armv8MMain => Isa::T32, + Arch::Armv7R => Isa::A32, + Arch::Armv8R => Isa::A32, + Arch::Armv7A => Isa::A32, + Arch::Armv8A => Isa::A64, + }) + } + + /// Get a comma-separated list of values, suitable for cfg-check + pub fn values() -> String { + let string_versions: Vec = [Isa::A64, Isa::A32, Isa::T32] + .iter() + .map(|i| format!(r#""{i}""#)) + .collect(); + string_versions.join(", ") + } +} + +impl core::fmt::Display for Isa { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Isa::A64 => "a64", + Isa::A32 => "a32", + Isa::T32 => "t32", + } + ) + } +} + +/// The Arm Architecture +/// +/// As defined by a particular revision of the Arm Architecture Reference Manual (ARM). +pub enum Arch { + /// Armv6-M (also known as ARMv6-M) + Armv6M, + /// Armv7-M (also known as ARMv7-M) + Armv7M, + /// Armv7E-M (also known as ARMv7E-M) + Armv7EM, + /// Armv8-M Baseline + Armv8MBase, + /// Armv8-M with Mainline extensions + Armv8MMain, + /// Armv7-R (also known as ARMv7-R) + Armv7R, + /// Armv8-R + Armv8R, + /// Armv7-A (also known as ARMv7-A) + Armv7A, + /// Armv8-A + Armv8A, +} + +impl Arch { + /// Decode a target string + pub fn get(target: &str) -> Option { + if target.starts_with("thumbv6m-") { + Some(Arch::Armv6M) + } else if target.starts_with("thumbv7m-") { + Some(Arch::Armv7M) + } else if target.starts_with("thumbv7em-") { + Some(Arch::Armv7EM) + } else if target.starts_with("thumbv8m.base-") { + Some(Arch::Armv8MBase) + } else if target.starts_with("thumbv8m.main-") { + Some(Arch::Armv8MMain) + } else if target.starts_with("armv7r-") || target.starts_with("armebv7r") { + Some(Arch::Armv7R) + } else if target.starts_with("armv8r-") { + Some(Arch::Armv8R) + } else if target.starts_with("aarch64-") || target.starts_with("aarch64be-") { + Some(Arch::Armv8A) + } else { + None + } + } + + /// Get the Arm Architecture Profile + pub fn profile(&self) -> Profile { + match self { + Arch::Armv6M | Arch::Armv7M | Arch::Armv7EM | Arch::Armv8MBase | Arch::Armv8MMain => { + Profile::M + } + Arch::Armv7R | Arch::Armv8R => Profile::R, + Arch::Armv7A | Arch::Armv8A => Profile::A, + } + } + + /// Get a comma-separated list of values, suitable for cfg-check + pub fn values() -> String { + let string_versions: Vec = [ + Arch::Armv6M, + Arch::Armv7M, + Arch::Armv7EM, + Arch::Armv8MBase, + Arch::Armv8MMain, + Arch::Armv7R, + Arch::Armv8R, + Arch::Armv7A, + Arch::Armv8A, + ] + .iter() + .map(|i| format!(r#""{i}""#)) + .collect(); + string_versions.join(", ") + } +} + +impl core::fmt::Display for Arch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Arch::Armv6M => "v6-m", + Arch::Armv7M => "v7-m", + Arch::Armv7EM => "v7e-m", + Arch::Armv7R => "v7-r", + Arch::Armv8R => "v8-r", + Arch::Armv8MBase => "v8-m.base", + Arch::Armv8MMain => "v8-m.main", + Arch::Armv7A => "v7-a", + Arch::Armv8A => "v8-a", + } + ) + } +} + +/// The Arm Architecture Profile. +pub enum Profile { + /// Microcontrollers + M, + /// Real-Time + R, + /// Applications + A, +} + +impl Profile { + /// Decode a target string + pub fn get(target: &str) -> Option { + let arch = Arch::get(target)?; + Some(arch.profile()) + } + + /// Get a comma-separated list of values, suitable for cfg-check + pub fn values() -> String { + let string_versions: Vec = [Profile::A, Profile::R, Profile::M] + .iter() + .map(|i| format!(r#""{i}""#)) + .collect(); + string_versions.join(", ") + } +} + +impl core::fmt::Display for Profile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Profile::M => "m", + Profile::R => "r", + Profile::A => "a", + } + ) + } +} diff --git a/ci/install.sh b/ci/install.sh deleted file mode 100644 index 80e8a70..0000000 --- a/ci/install.sh +++ /dev/null @@ -1,15 +0,0 @@ -set -euxo pipefail - -main() { - case $TARGET in - arm*v7r-none-eabi*) - rustup target add $TARGET - ;; - *) - mkdir gcc - curl -L https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2?revision=bc2c96c0-14b5-4bb4-9f18-bceb4050fee7?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,7-2018-q2-update | tar --strip-components=1 -C gcc -xj - ;; - esac -} - -main diff --git a/ci/script.sh b/ci/script.sh deleted file mode 100644 index f366c88..0000000 --- a/ci/script.sh +++ /dev/null @@ -1,7 +0,0 @@ -set -euxo pipefail - -main() { - cargo check -} - -main diff --git a/cortex-r-examples/.cargo/config.toml b/cortex-r-examples/.cargo/config.toml new file mode 100644 index 0000000..691059e --- /dev/null +++ b/cortex-r-examples/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = ["armv7r-none-eabihf"] diff --git a/cortex-r-examples/Cargo.toml b/cortex-r-examples/Cargo.toml new file mode 100644 index 0000000..9002856 --- /dev/null +++ b/cortex-r-examples/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors = ["Jonathan Pallant ", "The Cortex-R Team "] +default-run = "hello" +description = "Examples for Arm Cortex-R" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "cortex-r-examples" +readme = "README.md" +repository = "https://github.com/rust-embedded/cortex-r.git" +rust-version = "1.82" +version = "0.1.0" + +[dependencies] +arm-gic = {git = "https://github.com/google/arm-gic.git", rev = "46a8fc1720f5c28fccf4dfb5953b88dab7012e9c", optional = true} +cortex-r = {path = "../cortex-r", features = ["critical-section-single-core"]} +cortex-r-rt = {path = "../cortex-r-rt"} +semihosting = {version = "0.1.18", features = ["stdio"]} + +[build-dependencies] +arm-targets = {version = "0.1.0", path = "../arm-targets"} + +[features] +eabi-fpu = ["cortex-r-rt/eabi-fpu"] +gic = ["arm-gic"] + +[[bin]] +name = "gic" +required-features = ["gic"] diff --git a/cortex-r-examples/README.md b/cortex-r-examples/README.md new file mode 100644 index 0000000..7795ab8 --- /dev/null +++ b/cortex-r-examples/README.md @@ -0,0 +1,19 @@ +# Examples for Arm Cortex-R + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.82.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## Licence + +Copyright (c) Ferrous Systems, 2025 + +Licensed under either [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/cortex-r-examples/build.rs b/cortex-r-examples/build.rs new file mode 100644 index 0000000..e3ef318 --- /dev/null +++ b/cortex-r-examples/build.rs @@ -0,0 +1,34 @@ +//! # Build script for the Cortex-R Examples +//! +//! This script only executes when using `cargo` to build the project. +//! +//! Copyright (c) Ferrous Systems, 2025 + +use std::io::Write; + +fn main() { + arm_targets::process(); + + match std::env::var("TARGET").expect("TARGET not set").as_str() { + "armv8r-none-eabihf" => { + write("memory.x", include_bytes!("mps3-an536.ld")); + } + _ => { + write("memory.x", include_bytes!("versatileab.ld")); + } + } + // Use the cortex-m-rt linker script + println!("cargo:rustc-link-arg=-Tlink.x"); +} + +fn write(file: &str, contents: &[u8]) { + // Put linker file in our output directory and ensure it's on the + // linker search path. + let out = &std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + std::fs::File::create(out.join(file)) + .unwrap() + .write_all(contents) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed={}", file); +} diff --git a/cortex-r-examples/mps3-an536.ld b/cortex-r-examples/mps3-an536.ld new file mode 100644 index 0000000..95e1c36 --- /dev/null +++ b/cortex-r-examples/mps3-an536.ld @@ -0,0 +1,13 @@ +/* +Memory configuration for the MPS3-AN536 machine. + +See https://github.com/qemu/qemu/blob/master/hw/arm/mps3r.c +*/ + +MEMORY { + QSPI : ORIGIN = 0x08000000, LENGTH = 8M + DDR : ORIGIN = 0x20000000, LENGTH = 128M +} + +REGION_ALIAS("CODE", QSPI); +REGION_ALIAS("DATA", DDR); diff --git a/cortex-r-examples/reference/hello-armv7r-none-eabi.out b/cortex-r-examples/reference/hello-armv7r-none-eabi.out new file mode 100644 index 0000000..bc2fcbd --- /dev/null +++ b/cortex-r-examples/reference/hello-armv7r-none-eabi.out @@ -0,0 +1,11 @@ +Hello, this is semihosting! x = 1.000, y = 2.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "cortex-r-examples/src/bin/hello.rs", + line: 29, + col: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} diff --git a/cortex-r-examples/reference/hello-armv7r-none-eabihf.out b/cortex-r-examples/reference/hello-armv7r-none-eabihf.out new file mode 100644 index 0000000..bc2fcbd --- /dev/null +++ b/cortex-r-examples/reference/hello-armv7r-none-eabihf.out @@ -0,0 +1,11 @@ +Hello, this is semihosting! x = 1.000, y = 2.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "cortex-r-examples/src/bin/hello.rs", + line: 29, + col: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} diff --git a/cortex-r-examples/reference/registers-armv7r-none-eabi.out b/cortex-r-examples/reference/registers-armv7r-none-eabi.out new file mode 100644 index 0000000..4a54407 --- /dev/null +++ b/cortex-r-examples/reference/registers-armv7r-none-eabi.out @@ -0,0 +1,5 @@ +MIDR { implementer=0x41 variant=0x1 arch=0xf part_no=0xc15 rev=0x3 } +CPSR { N=0 Z=1 C=1 V=0 Q=0 J=0 E=0 A=1 I=1 F=1 T=0 MODE=Ok(Sys) } +_stack_top: 0x08000000 +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=0 FI=0 DZ=0 BR=0 RR=0 V=0 I=0 Z=0 SW=0 C=0 A=0 M=0 } before setting C, I and Z +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=0 FI=0 DZ=0 BR=0 RR=0 V=0 I=1 Z=1 SW=0 C=1 A=0 M=0 } after diff --git a/cortex-r-examples/reference/registers-armv7r-none-eabihf.out b/cortex-r-examples/reference/registers-armv7r-none-eabihf.out new file mode 100644 index 0000000..4a54407 --- /dev/null +++ b/cortex-r-examples/reference/registers-armv7r-none-eabihf.out @@ -0,0 +1,5 @@ +MIDR { implementer=0x41 variant=0x1 arch=0xf part_no=0xc15 rev=0x3 } +CPSR { N=0 Z=1 C=1 V=0 Q=0 J=0 E=0 A=1 I=1 F=1 T=0 MODE=Ok(Sys) } +_stack_top: 0x08000000 +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=0 FI=0 DZ=0 BR=0 RR=0 V=0 I=0 Z=0 SW=0 C=0 A=0 M=0 } before setting C, I and Z +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=0 FI=0 DZ=0 BR=0 RR=0 V=0 I=1 Z=1 SW=0 C=1 A=0 M=0 } after diff --git a/cortex-r-examples/reference/svc-armv7r-none-eabi.out b/cortex-r-examples/reference/svc-armv7r-none-eabi.out new file mode 100644 index 0000000..06f9803 --- /dev/null +++ b/cortex-r-examples/reference/svc-armv7r-none-eabi.out @@ -0,0 +1,14 @@ +x = 1, y = 2, z = 3.000 +In _svc_handler, with arg=0xabcdef +In _svc_handler, with arg=0x456789 +x = 1, y = 2, z = 3.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "cortex-r-examples/src/bin/svc.rs", + line: 32, + col: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} diff --git a/cortex-r-examples/reference/svc-armv7r-none-eabihf.out b/cortex-r-examples/reference/svc-armv7r-none-eabihf.out new file mode 100644 index 0000000..06f9803 --- /dev/null +++ b/cortex-r-examples/reference/svc-armv7r-none-eabihf.out @@ -0,0 +1,14 @@ +x = 1, y = 2, z = 3.000 +In _svc_handler, with arg=0xabcdef +In _svc_handler, with arg=0x456789 +x = 1, y = 2, z = 3.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "cortex-r-examples/src/bin/svc.rs", + line: 32, + col: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} diff --git a/cortex-r-examples/src/bin/gic.rs b/cortex-r-examples/src/bin/gic.rs new file mode 100644 index 0000000..efc7117 --- /dev/null +++ b/cortex-r-examples/src/bin/gic.rs @@ -0,0 +1,104 @@ +//! GIC example for Arm Cortex-R52 on an MPS2-AN336 + +#![no_std] +#![no_main] + +// pull in our start-up code +use cortex_r as _; +use cortex_r_examples as _; + +use arm_gic::{ + gicv3::{Group, SgiTarget}, + IntId, +}; +use semihosting::println; + +type SingleCoreGic = arm_gic::gicv3::GicV3<1>; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `cortex-m-rt`. +#[no_mangle] +pub extern "C" fn kmain() { + if let Err(e) = main() { + panic!("main returned {:?}", e); + } + semihosting::process::exit(0); +} + +/// Offset from PERIPHBASE for GIC Distributor +const GICD_BASE_OFFSET: usize = 0x0000_0000usize; + +/// Offset from PERIPHBASE for the first GIC Redistributor +const GICR_BASE_OFFSET: usize = 0x0010_0000usize; + +fn dump_cpsr() { + let cpsr = cortex_r::register::Cpsr::read(); + println!("CPSR: {:?}", cpsr); +} + +/// The main function of our Rust application. +/// +/// Called by [`kmain`]. +fn main() -> Result<(), core::fmt::Error> { + // Get the GIC address by reading CBAR + let periphbase = cortex_r::register::Cbar::read().periphbase(); + println!("Found PERIPHBASE {:010p}", periphbase); + let gicd_base = periphbase.wrapping_byte_add(GICD_BASE_OFFSET); + let gicr_base = periphbase.wrapping_byte_add(GICR_BASE_OFFSET); + + // Initialise the GIC. + println!( + "Creating GIC driver @ {:010p} / {:010p}", + gicd_base, gicr_base + ); + let mut gic: SingleCoreGic = + unsafe { SingleCoreGic::new(gicd_base.cast(), [gicr_base.cast()]) }; + println!("Calling git.setup(0)"); + gic.setup(0); + SingleCoreGic::set_priority_mask(0x80); + + // Configure a Software Generated Interrupt for Core 0 + println!("Configure SGI..."); + let sgi_intid = IntId::sgi(3); + gic.set_interrupt_priority(sgi_intid, Some(0), 0x31); + gic.set_group(sgi_intid, Some(0), Group::Group1NS); + + println!("gic.enable_interrupt()"); + gic.enable_interrupt(sgi_intid, Some(0), true); + + println!("Enabling interrupts..."); + dump_cpsr(); + unsafe { + cortex_r::interrupt::enable(); + } + dump_cpsr(); + + // Send it + println!("Send SGI"); + SingleCoreGic::send_sgi( + sgi_intid, + SgiTarget::List { + affinity3: 0, + affinity2: 0, + affinity1: 0, + target_list: 0b1, + }, + ); + + for _ in 0..1_000_000 { + cortex_r::asm::nop(); + } + + Ok(()) +} + +#[no_mangle] +unsafe extern "C" fn _irq_handler() { + println!("> IRQ"); + while let Some(int_id) = SingleCoreGic::get_and_acknowledge_interrupt() { + println!("- IRQ handle {:?}", int_id); + SingleCoreGic::end_interrupt(int_id); + } + println!("< IRQ"); +} diff --git a/cortex-r-examples/src/bin/hello.rs b/cortex-r-examples/src/bin/hello.rs new file mode 100644 index 0000000..944b518 --- /dev/null +++ b/cortex-r-examples/src/bin/hello.rs @@ -0,0 +1,30 @@ +//! Semihosting hello-world for Arm Cortex-R + +#![no_std] +#![no_main] + +// pull in our start-up code +use cortex_r as _; +use cortex_r_examples as _; + +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `cortex-m-rt`. +#[no_mangle] +pub extern "C" fn kmain() { + if let Err(e) = main() { + panic!("main returned {:?}", e); + } +} + +/// The main function of our Rust application. +/// +/// Called by [`kmain`]. +fn main() -> Result<(), core::fmt::Error> { + let x = 1.0f64; + let y = x * 2.0; + println!("Hello, this is semihosting! x = {:0.3}, y = {:0.3}", x, y); + panic!("I am an example panic"); +} diff --git a/cortex-r-examples/src/bin/registers.rs b/cortex-r-examples/src/bin/registers.rs new file mode 100644 index 0000000..227bfd5 --- /dev/null +++ b/cortex-r-examples/src/bin/registers.rs @@ -0,0 +1,45 @@ +//! Registers example for Arm Cortex-R + +#![no_std] +#![no_main] + +// pull in our start-up code +use cortex_r as _; +use cortex_r_examples as _; + +use semihosting::println; + +extern "C" { + static _stack_top: u32; +} + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `cortex-m-rt`. +#[no_mangle] +pub extern "C" fn kmain() { + println!("{:?}", cortex_r::register::Midr::read()); + println!("{:?}", cortex_r::register::Cpsr::read()); + #[cfg(arm_architecture = "v8-r")] + { + println!("{:?}", cortex_r::register::Cbar::read()); + println!("{:?}", cortex_r::register::Vbar::read()); + // This only works in EL2 and start-up put us in EL1 + // println!("{:?}", cortex_r::register::Hvbar::read()); + } + + println!("_stack_top: {:010p}", core::ptr::addr_of!(_stack_top)); + + println!( + "{:?} before setting C, I and Z", + cortex_r::register::Sctlr::read() + ); + cortex_r::register::Sctlr::modify(|w| { + w.set_c(true); + w.set_i(true); + w.set_z(true); + }); + println!("{:?} after", cortex_r::register::Sctlr::read()); + + semihosting::process::exit(0); +} diff --git a/cortex-r-examples/src/bin/svc.rs b/cortex-r-examples/src/bin/svc.rs new file mode 100644 index 0000000..f23b495 --- /dev/null +++ b/cortex-r-examples/src/bin/svc.rs @@ -0,0 +1,43 @@ +//! SVC (software interrupt) example for Arm Cortex-R + +#![no_std] +#![no_main] + +// pull in our start-up code +use cortex_r as _; +use cortex_r_examples as _; + +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `cortex-m-rt`. +#[no_mangle] +pub extern "C" fn kmain() { + if let Err(e) = main() { + panic!("main returned {:?}", e); + } +} + +/// The main function of our Rust application. +/// +/// Called by [`kmain`]. +fn main() -> Result<(), core::fmt::Error> { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + cortex_r::svc!(0xABCDEF); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + panic!("I am an example panic"); +} + +/// This is our SVC exception handler +#[no_mangle] +unsafe extern "C" fn _svc_handler(arg: u32) { + println!("In _svc_handler, with arg={:#06x}", arg); + if arg == 0xABCDEF { + // test nested SVC calls + cortex_r::svc!(0x456789); + } +} diff --git a/cortex-r-examples/src/lib.rs b/cortex-r-examples/src/lib.rs new file mode 100644 index 0000000..2d28291 --- /dev/null +++ b/cortex-r-examples/src/lib.rs @@ -0,0 +1,18 @@ +//! Common code for all examples + +#![no_std] + +// Need this to bring in the start-up function + +use cortex_r_rt as _; + +/// Called when the application raises an unrecoverable `panic!`. +/// +/// Prints the panic to the console and then exits QEMU using a semihosting +/// breakpoint. +#[panic_handler] +#[cfg(target_os = "none")] +fn panic(info: &core::panic::PanicInfo) -> ! { + semihosting::println!("PANIC: {:#?}", info); + semihosting::process::abort(); +} diff --git a/cortex-r-examples/versatileab.ld b/cortex-r-examples/versatileab.ld new file mode 100644 index 0000000..aced48a --- /dev/null +++ b/cortex-r-examples/versatileab.ld @@ -0,0 +1,12 @@ +/* +Memory configuration for the Arm Versatile Peripheral Board. + +See https://github.com/qemu/qemu/blob/master/hw/arm/versatilepb.c +*/ + +MEMORY { + SDRAM : ORIGIN = 0, LENGTH = 128M +} + +REGION_ALIAS("CODE", SDRAM); +REGION_ALIAS("DATA", SDRAM); diff --git a/cortex-r-rt/Cargo.toml b/cortex-r-rt/Cargo.toml new file mode 100644 index 0000000..4668dee --- /dev/null +++ b/cortex-r-rt/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = ["Jonathan Pallant ", "The Cortex-R Team "] +description = "Run-time support for Arm Cortex-R" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "cortex-r-rt" +readme = "README.md" +repository = "https://github.com/rust-embedded/cortex-r.git" +rust-version = "1.82" +version = "0.1.0" + +[dependencies] +cortex-r = {version = "0.1.0", path = "../cortex-r"} +semihosting = {version = "0.1.18", features = ["stdio"]} + +[features] +# Enable the FPU on start-up, even on a soft-float EABI target +eabi-fpu = [] + +[build-dependencies] +arm-targets = {version = "0.1.0", path = "../arm-targets"} diff --git a/cortex-r-rt/README.md b/cortex-r-rt/README.md new file mode 100644 index 0000000..4f4f194 --- /dev/null +++ b/cortex-r-rt/README.md @@ -0,0 +1,19 @@ +# Arm Cortex-R Run-Time + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.82.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## Licence + +Copyright (c) Ferrous Systems, 2025 + +Licensed under either [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/cortex-r-rt/build.rs b/cortex-r-rt/build.rs new file mode 100644 index 0000000..75fbd87 --- /dev/null +++ b/cortex-r-rt/build.rs @@ -0,0 +1,24 @@ +//! # Build script for the Cortex-R Runtime +//! +//! This script only executes when using `cargo` to build the project. +//! +//! Copyright (c) Ferrous Systems, 2025 + +use std::io::Write; + +fn main() { + arm_targets::process(); + write("link.x", include_bytes!("link.x")); +} + +fn write(file: &str, contents: &[u8]) { + // Put linker file in our output directory and ensure it's on the + // linker search path. + let out = &std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + std::fs::File::create(out.join(file)) + .unwrap() + .write_all(contents) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed={}", file); +} diff --git a/cortex-r-rt/link.x b/cortex-r-rt/link.x new file mode 100644 index 0000000..2b31616 --- /dev/null +++ b/cortex-r-rt/link.x @@ -0,0 +1,95 @@ +/* +Basic Cortex-R linker script. + +You must supply a file called `memory.x` which defines the memory regions 'CODE' and 'DATA'. + +The stack pointer(s) will be (near) the top of the DATA region by default. + +Based upon the linker script from https://github.com/rust-embedded/cortex-m +*/ + +INCLUDE memory.x + +ENTRY(_vector_table); +EXTERN(_vector_table); + +SECTIONS { + .text : { + /* The vector table must come first */ + *(.vector_table) + /* Our exception handling routines */ + *(.text.handlers) + /* Now the rest of the code */ + *(.text .text*) + } > CODE + + .rodata : { + *(.rodata .rodata*) + } > CODE + + .data : ALIGN(4) { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); + } > DATA AT>CODE + /* + * Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! + */ + . = ALIGN(4); + __edata = .; + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + .bss (NOLOAD) : ALIGN(4) { + . = ALIGN(4); + __sbss = .; + *(.bss .bss* COMMON) + . = ALIGN(4); + } > DATA + /* + * Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! + */ + __ebss = .; + + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > DATA + + /DISCARD/ : { + *(.note .note*) + } +} + +/* +We reserve some space at the top of the RAM for our stacks. We have an IRQ stack +and a FIQ stack, plus the remainder is our system stack. + +You must keep _stack_top and the stack sizes aligned to eight byte boundaries. +*/ +PROVIDE(_stack_top = ORIGIN(DATA) + LENGTH(DATA)); +PROVIDE(_fiq_stack_size = 0x100); +PROVIDE(_irq_stack_size = 0x1000); +PROVIDE(_svc_stack_size = 0x1000); + +ASSERT(_stack_top % 8 == 0, "ERROR(cortex-r-rt): top of stack is not 8-byte aligned"); +ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of FIQ stack is not 8-byte aligned"); +ASSERT(_irq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of IRQ stack is not 8-byte aligned"); + +PROVIDE(_asm_undefined_handler =_asm_default_handler); +PROVIDE(_asm_prefetch_handler =_asm_default_handler); +PROVIDE(_asm_abort_handler =_asm_default_handler); +PROVIDE(_asm_fiq_handler =_asm_default_fiq_handler); +PROVIDE(_irq_handler =_default_handler); +PROVIDE(_svc_handler =_default_handler); +PROVIDE(_start =_default_start); diff --git a/cortex-r-rt/src/lib.rs b/cortex-r-rt/src/lib.rs new file mode 100644 index 0000000..a59c526 --- /dev/null +++ b/cortex-r-rt/src/lib.rs @@ -0,0 +1,518 @@ +//! Run-time support for Arm Cortex-R +//! +//! This library implements a simple Arm vector table, suitable for getting into +//! a Rust application running in System Mode. +//! +//! Transferring from System Mode to User Mode (i.e. implementing an RTOS) is +//! not handled here. +//! +//! If your processor starts in Hyp mode, this runtime will be transfer it to +//! System mode. If you wish to write a hypervisor, you will need to replace +//! this library with something more advanced. +//! +//! We assume the following global symbols exist: +//! +//! * `__start` - a Reset handler. Our linker script PROVIDEs a default function +//! at `_default_start` but you can override it. +//! * `_stack_top` - the address of the top of some region of RAM that we can +//! use as stack space, with eight-byte alignment. Our linker script PROVIDEs +//! a default pointing at the top of RAM. +//! * `_fiq_stack_size` - the number of bytes to be reserved for stack space +//! when in FIQ mode; must be a multiple of 8. +//! * `_irq_stack_size` - the number of bytes to be reserved for stack space +//! when in FIQ mode; must be a multiple of 8. +//! * `_svc_stack_size` - the number of bytes to be reserved for stack space +//! when in SVC mode; must be a multiple of 8.F +//! * `_svc_handler` - an `extern "C"` function to call when an SVC Exception +//! occurs. Our linker script PROVIDEs a default function at +//! `_default_handler` but you can override it. +//! * `_irq_handler` - an `extern "C"` function to call when an Interrupt +//! occurs. Our linker script PROVIDEs a default function at +//! `_default_handler` but you can override it. +//! * `_asm_fiq_handler` - a naked function to call when a Fast Interrupt +//! Request (FIQ) occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_fiq_handler` but you can override it. +//! * `_asm_undefined_handler` - a naked function to call when an Undefined +//! Exception occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_handler` but you can override it. +//! * `_asm_prefetch_handler` - a naked function to call when an Prefetch +//! Exception occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_handler` but you can override it. +//! * `_asm_abort_handler` - a naked function to call when an Abort Exception +//! occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_handler` but you can override it. +//! * `kmain` - the `extern "C"` entry point to your application. +//! * `__sdata` - the start of initialised data in RAM. Must be 4-byte aligned. +//! * `__edata` - the end of initialised data in RAM. Must be 4-byte aligned. +//! * `__sidata` - the start of the initialisation values for data, in read-only +//! memory. Must be 4-byte aligned. +//! * `__sbss` - the start of zero-initialised data in RAM. Must be 4-byte +//! aligned. +//! * `__ebss` - the end of zero-initialised data in RAM. Must be 4-byte +//! aligned. +//! +//! On start-up, the memory between `__sbss` and `__ebss` is zeroed, and the +//! memory between `__sdata` and `__edata` is initialised with the data found at +//! `__sidata`. +//! +//! This library produces global symbols called: +//! +//! * `_vector_table` - the start of the interrupt vector table +//! * `_default_start` - the default Reset handler, that sets up some stacks and +//! calls an `extern "C"` function called `kmain`. +//! * `_asm_default_fiq_handler` - an FIQ handler that just spins +//! * `_asm_default_handler` - an exception handler that just spins +//! * `_asm_svc_handler` - assembly language trampoline for SVC Exceptions that +//! calls `_svc_handler` +//! * `_asm_irq_handler` - assembly language trampoline for Interrupts that +//! calls `_irq_handler` +//! +//! The assembly language trampolines are required because Armv7-R (and Armv8-R) +//! processors do not save a great deal of state on entry to an exception +//! handler, unlike Armv7-M (and other M-Profile) processors. We must therefore +//! save this state to the stack using assembly language, before transferring to +//! an `extern "C"` function. We do not change modes before entering that +//! `extern "C"` function - that's for the handler to deal with as it wishes. We +//! supply a default handler that prints an error message to Semihosting so you +//! know if you hit an unexpected exception. Because FIQ is often +//! performance-sensitive, we don't supply an FIQ trampoline; if you want to use +//! FIQ, you have to write your own assembly routine, allowing you to preserve +//! only whatever state is important to you. +//! +//! If our start-up routine doesn't work for you (e.g. if you have to initialise +//! your memory controller before you touch RAM), supply your own `_start` +//! function (but feel free to call our `_default_start` as part of it). + +#![no_std] + +use cortex_r::register::{cpsr::ProcessorMode, Cpsr}; + +#[cfg(arm_architecture = "v8-r")] +use cortex_r::register::Hactlr; + +/// Our default exception handler. +/// +/// We end up here if an exception fires and the weak 'PROVIDE' in the link.x +/// file hasn't been over-ridden. +#[no_mangle] +pub extern "C" fn _default_handler() { + semihosting::eprintln!("Unhandled exception!"); + semihosting::process::abort(); +} + +// The Interrupt Vector Table, and some default assembly-language handler. +#[cfg(any(arm_architecture = "v7-r", arm_architecture = "v8-r"))] +core::arch::global_asm!( + r#" + .section .vector_table + .align 0 + + .global _vector_table + .type _vector_table, %function + _vector_table: + ldr pc, =_start + ldr pc, =_asm_undefined_handler + ldr pc, =_asm_svc_handler + ldr pc, =_asm_prefetch_handler + ldr pc, =_asm_abort_handler + nop + ldr pc, =_asm_irq_handler + ldr pc, =_asm_fiq_handler + .size _vector_table, . - _vector_table + + .section .text.handlers + + .global _asm_default_fiq_handler + .type _asm_default_fiq_handler, %function + _asm_default_fiq_handler: + b _asm_default_fiq_handler + .size _asm_default_fiq_handler, . - _asm_default_fiq_handler + + .global _asm_default_handler + .type _asm_default_handler, %function + _asm_default_handler: + b _asm_default_handler + .size _asm_default_handler, . - _asm_default_handler + "# +); + +/// This macro expands to code for saving context on entry to an exception +/// handler. +/// +/// It should match `restore_context!`. +/// +/// On entry to this block, we assume that we are in exception context. +#[cfg(all( + any(arm_architecture = "v7-r", arm_architecture = "v8-r"), + not(any(target_abi = "eabihf", feature = "eabi-fpu")) +))] +macro_rules! save_context { + () => { + r#" + // save preserved registers (and gives us some working area) + push {{r0-r3}} + // align SP down to eight byte boundary + mov r0, sp + and r0, r0, 7 + sub sp, r0 + // push alignment amount, and final preserved register + push {{r0, r12}} + "# + }; +} + +/// This macro expands to code for restoring context on exit from an exception +/// handler. +/// +/// It should match `save_context!`. +#[cfg(all( + any(arm_architecture = "v7-r", arm_architecture = "v8-r"), + not(any(target_abi = "eabihf", feature = "eabi-fpu")) +))] +macro_rules! restore_context { + () => { + r#" + // restore alignment amount, and preserved register + pop {{r0, r12}} + // restore pre-alignment SP + add sp, r0 + // restore more preserved registers + pop {{r0-r3}} + "# + }; +} + +/// This macro expands to code for saving context on entry to an exception +/// handler. +/// +/// It should match `restore_context!`. +#[cfg(all( + any(arm_architecture = "v7-r", arm_architecture = "v8-r"), + any(target_abi = "eabihf", feature = "eabi-fpu") +))] +macro_rules! save_context { + () => { + r#" + // save preserved registers (and gives us some working area) + push {{r0-r3}} + // save FPU context + vpush {{d0-d7}} + vmrs r0, FPSCR + vmrs r1, FPEXC + push {{r0-r1}} + // align SP down to eight byte boundary + mov r0, sp + and r0, r0, 7 + sub sp, r0 + // push alignment amount, and final preserved register + push {{r0, r12}} + "# + }; +} + +/// This macro expands to code for restoring context on exit from an exception +/// handler. +/// +/// It should match `save_context!`. +#[cfg(all( + any(arm_architecture = "v7-r", arm_architecture = "v8-r"), + any(target_abi = "eabihf", feature = "eabi-fpu") +))] +macro_rules! restore_context { + () => { + r#" + // restore alignment amount, and preserved register + pop {{r0, r12}} + // restore pre-alignment SP + add sp, r0 + // pop FPU state + pop {{r0-r1}} + vmsr FPEXC, r1 + vmsr FPSCR, r0 + vpop {{d0-d7}} + // restore more preserved registers + pop {{r0-r3}} + "# + }; +} + +// Our assembly language exception handlers +#[cfg(any(arm_architecture = "v7-r", arm_architecture = "v8-r"))] +core::arch::global_asm!( + r#" + .section .text.handlers + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3-d16 + .align 0 + + // Called from the vector table when we have an software interrupt. + // Saves state and calls a C-compatible handler like + // `extern "C" fn svc_handler(svc: u32, context: *const u32);` + .global _asm_svc_handler + .type _asm_svc_handler, %function + _asm_svc_handler: + srsfd sp!, {svc_mode} + "#, + save_context!(), + r#" + mrs r0, cpsr // Load processor status + tst r0, {t_bit} // Occurred in Thumb state? + ldrhne r0, [lr,#-2] // Yes: Load halfword and... + bicne r0, r0, #0xFF00 // ...extract comment field + ldreq r0, [lr,#-4] // No: Load word and... + biceq r0, r0, #0xFF000000 // ...extract comment field + // r0 now contains SVC number + bl _svc_handler + "#, + restore_context!(), + r#" + rfefd sp! + .size _asm_svc_handler, . - _asm_svc_handler + + // Called from the vector table when we have an interrupt. + // Saves state and calls a C-compatible handler like + // `extern "C" fn irq_handler();` + .global _asm_irq_handler + .type _asm_irq_handler, %function + _asm_irq_handler: + sub lr, lr, 4 + srsfd sp!, {irq_mode} + "#, + save_context!(), + r#" + // call C handler + bl _irq_handler + "#, + restore_context!(), + r#" + rfefd sp! + .size _asm_irq_handler, . - _asm_irq_handler + "#, + svc_mode = const ProcessorMode::Svc as u8, + irq_mode = const ProcessorMode::Irq as u8, + t_bit = const { + Cpsr::new_with_raw_value(0) + .with_t(true) + .raw_value() + }, +); + +/// This macro expands to code to turn on the FPU +#[cfg(all( + any(arm_architecture = "v7-r", arm_architecture = "v8-r"), + any(target_abi = "eabihf", feature = "eabi-fpu") +))] +macro_rules! fpu_enable { + () => { + r#" + // Allow VFP coprocessor access + mrc p15, 0, r0, c1, c0, 2 + orr r0, r0, #0xF00000 + mcr p15, 0, r0, c1, c0, 2 + // Enable VFP + mov r0, #0x40000000 + vmsr fpexc, r0 + "# + }; +} + +/// This macro expands to code that does nothing because there is no FPU +#[cfg(all( + any(arm_architecture = "v7-r", arm_architecture = "v8-r"), + not(any(target_abi = "eabihf", feature = "eabi-fpu")) +))] +macro_rules! fpu_enable { + () => { + r#" + // no FPU - do nothing + "# + }; +} + +// Start-up code for Armv7-R (and Armv8-R once we've left EL2) +// +// We set up our stacks and `kmain` in system mode. +#[cfg(any(arm_architecture = "v7-r", arm_architecture = "v8-r"))] +core::arch::global_asm!( + r#" + .section .text.startup + .align 0 + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3-d16 + + .type _el1_start, %function + _el1_start: + // Set stack pointer (as the top) and mask interrupts for for FIQ mode (Mode 0x11) + ldr r0, =_stack_top + msr cpsr, {fiq_mode} + mov sp, r0 + ldr r1, =_fiq_stack_size + sub r0, r0, r1 + // Set stack pointer (right after) and mask interrupts for for IRQ mode (Mode 0x12) + msr cpsr, {irq_mode} + mov sp, r0 + ldr r1, =_irq_stack_size + sub r0, r0, r1 + // Set stack pointer (right after) and mask interrupts for for SVC mode (Mode 0x13) + msr cpsr, {svc_mode} + mov sp, r0 + ldr r1, =_svc_stack_size + sub r0, r0, r1 + // Set stack pointer (right after) and mask interrupts for for System mode (Mode 0x1F) + msr cpsr, {sys_mode} + mov sp, r0 + // Clear the Thumb Exception bit because we're in Arm mode + mrc p15, 0, r0, c1, c0, 0 + bic r0, #{te_bit} + mcr p15, 0, r0, c1, c0, 0 + "#, + fpu_enable!(), + r#" + // Initialise .bss + ldr r0, =__sbss + ldr r1, =__ebss + mov r2, 0 + 0: + cmp r1, r0 + beq 1f + stm r0!, {{r2}} + b 0b + 1: + // Initialise .data + ldr r0, =__sdata + ldr r1, =__edata + ldr r2, =__sidata + 0: + cmp r1, r0 + beq 1f + ldm r2!, {{r3}} + stm r0!, {{r3}} + b 0b + 1: + // Jump to application + bl kmain + // In case the application returns, loop forever + b . + .size _el1_start, . - _el1_start + "#, + fiq_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Fiq) + .with_i(true) + .with_f(true) + .raw_value() + }, + irq_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Irq) + .with_i(true) + .with_f(true) + .raw_value() + }, + svc_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Svc) + .with_i(true) + .with_f(true) + .raw_value() + }, + sys_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Sys) + .with_i(true) + .with_f(true) + .raw_value() + }, + te_bit = const { + cortex_r::register::Sctlr::new_with_raw_value(0) + .with_te(true) + .raw_value() + } +); + +// Start-up code for Armv7-R. +// +// Go straight to our default routine +#[cfg(arm_architecture = "v7-r")] +core::arch::global_asm!( + r#" + .section .text.startup + .align 0 + + .global _default_start + .type _default_start, %function + _default_start: + ldr pc, =_el1_start + .size _default_start, . - _default_start + "# +); + +// Start-up code for Armv8-R. +// +// There's only one Armv8-R CPU (the Cortex-R52) and the FPU is mandatory, so we +// always enable it. +// +// We boot into EL2, set up a stack pointer, and run `kmain` in EL1. +#[cfg(arm_architecture = "v8-r")] +core::arch::global_asm!( + r#" + .section .text.startup + .align 0 + + .global _default_start + .type _default_start, %function + _default_start: + // Are we in EL2? If not, skip the EL2 setup portion + mrs r0, cpsr + and r0, r0, 0x1F + cmp r0, {cpsr_mode_hyp} + bne 1f + // Set stack pointer + ldr sp, =_stack_top + // Set the HVBAR (for EL2) to _vector_table + ldr r0, =_vector_table + mcr p15, 4, r0, c12, c0, 0 + // Configure HACTLR to let us enter EL1 + mrc p15, 4, r0, c1, c0, 1 + mov r1, {hactlr_bits} + orr r0, r0, r1 + mcr p15, 4, r0, c1, c0, 1 + // Program the SPSR - enter system mode (0x1F) in Arm mode with IRQ, FIQ masked + mov r0, {sys_mode} + msr spsr_hyp, r0 + adr r0, 1f + msr elr_hyp, r0 + dsb + isb + eret + 1: + // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on + // Armv7-R because that only supports 'low' (default) or 'high'. + ldr r0, =_vector_table + mcr p15, 0, r0, c12, c0, 0 + // go do the rest of the EL1 init + ldr pc, =_el1_start + .size _default_start, . - _default_start + "#, + cpsr_mode_hyp = const ProcessorMode::Hyp as u8, + hactlr_bits = const { + Hactlr::new_with_raw_value(0) + .with_cpuactlr(true) + .with_cdbgdci(true) + .with_flashifregionr(true) + .with_periphpregionr(true) + .with_qosr(true) + .with_bustimeoutr(true) + .with_intmonr(true) + .with_err(true) + .with_testr1(true) + .raw_value() + }, + sys_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Sys) + .with_i(true) + .with_f(true) + .raw_value() + } +); diff --git a/cortex-r/Cargo.toml b/cortex-r/Cargo.toml new file mode 100644 index 0000000..4b2469c --- /dev/null +++ b/cortex-r/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = ["Jonathan Pallant ", "The Cortex-R Team "] +description = "CPU support for Arm Cortex-R" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "cortex-r" +readme = "README.md" +repository = "https://github.com/rust-embedded/cortex-r.git" +rust-version = "1.82" +version = "0.1.0" + +[dependencies] +arbitrary-int = "1.3.0" +bitbybit = "1.3.3" +critical-section = {version = "1.2.0", features = ["restore-state-bool"], optional = true} +defmt = {version = "0.3", optional = true} + +[build-dependencies] +arm-targets = {version = "0.1.0", path = "../arm-targets"} + +[features] +# Adds a critical-section implementation that only disables interrupts. +# This is not sound on multi-core systems because interrupts are per-core. +critical-section-single-core = ["critical-section"] +# Adds defmt::Format implementation for the register types +defmt = ["dep:defmt"] diff --git a/cortex-r/README.md b/cortex-r/README.md new file mode 100644 index 0000000..0270e58 --- /dev/null +++ b/cortex-r/README.md @@ -0,0 +1,19 @@ +# Support for Arm Cortex-R + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.82.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## Licence + +Copyright (c) Ferrous Systems, 2025 + +Licensed under either [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/cortex-r/build.rs b/cortex-r/build.rs new file mode 100644 index 0000000..9306423 --- /dev/null +++ b/cortex-r/build.rs @@ -0,0 +1,9 @@ +//! # Build script for the Cortex-R library +//! +//! This script only executes when using `cargo` to build the project. +//! +//! Copyright (c) Ferrous Systems, 2025 + +fn main() { + arm_targets::process(); +} diff --git a/cortex-r/src/asm.rs b/cortex-r/src/asm.rs new file mode 100644 index 0000000..e396c6d --- /dev/null +++ b/cortex-r/src/asm.rs @@ -0,0 +1,41 @@ +//! Simple assembly routines + +/// Emit an DSB instruction +#[inline] +pub fn dsb() { + unsafe { + core::arch::asm!("dsb"); + } +} + +/// Emit an ISB instruction +#[inline] +pub fn isb() { + unsafe { + core::arch::asm!("isb"); + } +} + +/// Emit an NOP instruction +#[inline] +pub fn nop() { + unsafe { + core::arch::asm!("nop"); + } +} + +/// Emit an WFI instruction +#[inline] +pub fn wfi() { + unsafe { + core::arch::asm!("wfi"); + } +} + +/// Emit an WFE instruction +#[inline] +pub fn wfe() { + unsafe { + core::arch::asm!("wfe"); + } +} diff --git a/cortex-r/src/critical_section.rs b/cortex-r/src/critical_section.rs new file mode 100644 index 0000000..1030148 --- /dev/null +++ b/cortex-r/src/critical_section.rs @@ -0,0 +1,30 @@ +//! Code that implements the `critical-section` traits on Cortex-R. +//! +//! Only valid if you have a single core. + +use core::sync::atomic; + +struct SingleCoreCriticalSection; +critical_section::set_impl!(SingleCoreCriticalSection); + +unsafe impl critical_section::Impl for SingleCoreCriticalSection { + unsafe fn acquire() -> critical_section::RawRestoreState { + // the i bit means "masked" + let was_active = !crate::register::Cpsr::read().i(); + crate::interrupt::disable(); + atomic::compiler_fence(atomic::Ordering::SeqCst); + was_active + } + + unsafe fn release(was_active: critical_section::RawRestoreState) { + // Only re-enable interrupts if they were enabled before the critical section. + if was_active { + atomic::compiler_fence(atomic::Ordering::SeqCst); + // Safety: This is OK because we're releasing a lock that was + // entered with interrupts enabled + unsafe { + crate::interrupt::enable(); + } + } + } +} diff --git a/cortex-r/src/interrupt.rs b/cortex-r/src/interrupt.rs new file mode 100644 index 0000000..455e29d --- /dev/null +++ b/cortex-r/src/interrupt.rs @@ -0,0 +1,59 @@ +//! Interrupts on Arm Cortex-R + +use core::sync::atomic::{compiler_fence, Ordering}; + +/// Enable interrupts +/// +/// * Doesn't work in User mode. +/// * Doesn't enable FIQ. +/// +/// # Safety +/// +/// Do not call this function inside an interrupt-based critical section +#[inline] +pub unsafe fn enable() { + // Ensure no preceeding memory accesses are reordered to after interrupts are enabled. + compiler_fence(Ordering::SeqCst); + // Safety: We're atomically setting a bit in a special register, and we're + // in an unsafe function that places restrictions on when you can call it + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("dsb", "cpsie i", options(nomem, nostack, preserves_flags)); + }; +} + +/// Disable IRQ +/// +/// * Doesn't work in User mode. +/// * Doesn't disable FIQ. +#[inline] +pub fn disable() { + // Safety: We're atomically clearing a bit in a special register + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("cpsid i", "dsb", options(nomem, nostack, preserves_flags)); + }; + // Ensure no subsequent memory accesses are reordered to before interrupts are disabled. + compiler_fence(Ordering::SeqCst); +} + +/// Run with interrupts disabled +/// +/// * Doesn't work in User mode. +/// * Doesn't disable FIQ. +#[inline] +pub fn free(f: F) -> T +where + F: FnOnce() -> T, +{ + let cpsr = crate::register::Cpsr::read(); + disable(); + let result = f(); + if cpsr.i() { + // Safety: We're only turning them back on if they were on previously + unsafe { + enable(); + } + } + result +} diff --git a/cortex-r/src/lib.rs b/cortex-r/src/lib.rs new file mode 100644 index 0000000..3757cbe --- /dev/null +++ b/cortex-r/src/lib.rs @@ -0,0 +1,25 @@ +//! CPU/peripheral support for Arm Cortex-R + +#![no_std] + +#[cfg(feature = "critical-section-single-core")] +mod critical_section; + +pub mod register; + +pub mod interrupt; + +pub mod asm; + +/// Generate an SVC call with the given argument. +/// +/// Safe to call even in Supervisor (Svc) mode, as long as your Svc handler +/// saves and restores SPSR_svc correctly. +#[macro_export] +macro_rules! svc { + ($r0:expr) => { + unsafe { + core::arch::asm!("svc {arg}", arg = const $r0, out("lr") _); + } + } +} diff --git a/cortex-r/src/register/armv8r/cbar.rs b/cortex-r/src/register/armv8r/cbar.rs new file mode 100644 index 0000000..7ea95cf --- /dev/null +++ b/cortex-r/src/register/armv8r/cbar.rs @@ -0,0 +1,40 @@ +//! Code for the *Configuration Base Address Register* + +/// The *Configuration Base Address Register* (CBAR) +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Cbar(u32); + +impl Cbar { + /// Reads the *Configuration Base Address Register* + #[inline] + pub fn read() -> Cbar { + let r: u32; + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrc p15, 1, {}, c15, c3, 0", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self(r) + } + + /// Get the periphbase address + pub fn periphbase(self) -> *mut u32 { + (self.0 & 0xFFF00000) as *mut u32 + } +} + +impl core::fmt::Debug for Cbar { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CBAR {{ {:010p} }}", self.periphbase()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Cbar { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "CBAR {{ 0x{=usize:08x} }}", self.0 as usize) + } +} diff --git a/cortex-r/src/register/armv8r/hactlr.rs b/cortex-r/src/register/armv8r/hactlr.rs new file mode 100644 index 0000000..e2aa20b --- /dev/null +++ b/cortex-r/src/register/armv8r/hactlr.rs @@ -0,0 +1,96 @@ +//! Code for managing the *Hyp Auxiliary Control Register* + +/// The *Hyp Auxiliary Control Register* (HACTRL) +#[bitbybit::bitfield(u32)] +pub struct Hactlr { + /// Controls access to IMP_TESTR1 at EL0 and EL1 + #[bits(15..=15, rw)] + testr1: bool, + /// Controls access to IMP_DCERR0, IMP_DCERR1, IMP_ICERR0, IMP_ICERR1, + /// IMP_TCMERR0, IMP_TCMERR1, IMP_FLASHERR0, and IMP_FLASHERR1 registers + #[bits(13..=13, rw)] + err: bool, + /// Controls access to IMP_INTMONR at EL1 + #[bits(12..=12, rw)] + intmonr: bool, + /// Controls access to IMP_BUSTIMEOUTR at EL1 + #[bits(10..=10, rw)] + bustimeoutr: bool, + /// Controls access to QOSR at EL1 + #[bits(9..=9, rw)] + qosr: bool, + /// Controls access to IMP_PERIPHPREGIONR at EL1 + #[bits(8..=8, rw)] + periphpregionr: bool, + /// Controls access to IMP_FLASHIFREGIONR at EL1 + #[bits(7..=7, rw)] + flashifregionr: bool, + /// Controls access to CDBGDCI at EL1 + #[bits(1..=1, rw)] + cdbgdci: bool, + /// IMP_CPUACTLR write access control + #[bits(0..=0, rw)] + cpuactlr: bool, +} + +impl Hactlr { + /// Reads the *Hyp Auxiliary Control Register* + #[inline] + pub fn read() -> Hactlr { + let r: u32; + // Safety: Reading this register has no side-effects and is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrc p15, 4, {}, c1, c0, 1", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self::new_with_raw_value(r) + } + + /// Write to the *Hyp Auxiliary Control Register* + #[inline] + pub fn write(_value: Self) { + // Safety: Writing this register is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mcr p15, 4, {}, c1, c0, 1", in(reg) _value.raw_value(), options(nomem, nostack, preserves_flags)); + }; + } + + /// Modify the *Hyp Auxiliary Control Register* + #[inline] + pub fn modify(f: F) + where + F: FnOnce(&mut Self), + { + let mut value = Self::read(); + f(&mut value); + Self::write(value); + } +} + +impl core::fmt::Debug for Hactlr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "HACTLR {{ CPUACTLR={}, CDBGDCI={}, FLASHIFREGIONR={}, PERIPHPREGIONR={}, QOSR={}, BUSTIMEOUTR={}, INTMONR={}, ERR={}, TESTR1={} }}", + self.cpuactlr() as u8, + self.cdbgdci() as u8, + self.flashifregionr() as u8, + self.periphpregionr() as u8, + self.qosr() as u8, + self.bustimeoutr() as u8, + self.intmonr() as u8, + self.err() as u8, + self.testr1() as u8 + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Hactlr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "HACTLR {{ CPUACTLR={0=0..1}, CDBGDCI={0=1..2}, FLASHIFREGIONR={0=7..8}, PERIPHPREGIONR={0=8..9}, QOSR={0=9..10}, BUSTIMEOUTR={0=10..11}, INTMONR={0=12..13}, ERR={0=13..14}, TESTR1={0=15..16} }}", self.0) + } +} diff --git a/cortex-r/src/register/armv8r/hvbar.rs b/cortex-r/src/register/armv8r/hvbar.rs new file mode 100644 index 0000000..f464c2b --- /dev/null +++ b/cortex-r/src/register/armv8r/hvbar.rs @@ -0,0 +1,57 @@ +//! Code for the *Hyp Vector Base Address Register* + +/// The *Hyp Vector Base Address Register* (Hvbar) +/// +/// There is no `modify` method because this register holds a single 32-bit address. +/// +/// This is only available in EL2. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct Hvbar(*mut u32); + +impl Hvbar { + /// Reads the *Hyp Vector Base Address Register* + /// + /// Will cause an exception unless you are in EL2. + #[inline] + pub fn read() -> Hvbar { + let r: usize; + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrc p15, 4, {}, c12, c0, 0", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self(r as *mut u32) + } + + /// Write to the *Hyp Vector Base Address Register* + /// + /// # Safety + /// + /// You must supply a correctly-aligned address of a valid Arm Cortex-R + /// Vector Table. + #[inline] + pub fn write(_value: Self) { + // Safety: Writing this register is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mcr p15, 0, {}, c12, c0, 0", in(reg) _value.0, options(nomem, nostack, preserves_flags)); + }; + } +} + +impl core::fmt::Debug for Hvbar { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "HVBAR {{ {:010p} }}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Hvbar { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "HVBAR {{ 0x{=usize:08x} }}", self.0 as usize) + } +} diff --git a/cortex-r/src/register/armv8r/mod.rs b/cortex-r/src/register/armv8r/mod.rs new file mode 100644 index 0000000..642b3d7 --- /dev/null +++ b/cortex-r/src/register/armv8r/mod.rs @@ -0,0 +1,17 @@ +//! Access registers for Armv8-R only + +mod cbar; +#[doc(inline)] +pub use cbar::Cbar; + +mod hactlr; +#[doc(inline)] +pub use hactlr::Hactlr; + +mod hvbar; +#[doc(inline)] +pub use hvbar::Hvbar; + +mod vbar; +#[doc(inline)] +pub use vbar::Vbar; diff --git a/cortex-r/src/register/armv8r/vbar.rs b/cortex-r/src/register/armv8r/vbar.rs new file mode 100644 index 0000000..00b8546 --- /dev/null +++ b/cortex-r/src/register/armv8r/vbar.rs @@ -0,0 +1,53 @@ +//! Code for the *Vector Base Address Register* + +/// The *Vector Base Address Register* (VBAR) +/// +/// There is no `modify` method because this register holds a single 32-bit address. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct Vbar(pub *mut u32); + +impl Vbar { + /// Reads the *Vector Base Address Register* + #[inline] + pub fn read() -> Vbar { + let r: usize; + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrc p15, 0, {}, c12, c0, 0", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self(r as *mut u32) + } + + /// Write to the *Vector Base Address Register* + /// + /// # Safety + /// + /// You must supply a correctly-aligned address of a valid Arm Cortex-R + /// Vector Table. + #[inline] + pub unsafe fn write(_value: Self) { + // Safety: Writing this register is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mcr p15, 0, {}, c12, c0, 0", in(reg) _value.0, options(nomem, nostack, preserves_flags)); + }; + } +} + +impl core::fmt::Debug for Vbar { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "VBAR {{ {:010p} }}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Vbar { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "VBAR {{ 0x{=usize:08x} }}", self.0 as usize) + } +} diff --git a/cortex-r/src/register/cpsr.rs b/cortex-r/src/register/cpsr.rs new file mode 100644 index 0000000..3f5c7a6 --- /dev/null +++ b/cortex-r/src/register/cpsr.rs @@ -0,0 +1,112 @@ +//! Code for managing the *Current Program Status Register* + +/// The current Processor Mode +#[derive(Debug)] +#[bitbybit::bitenum(u5, exhaustive = false)] +pub enum ProcessorMode { + /// User Mode + Usr = 0b10000, + /// FIQ Mode + Fiq = 0b10001, + /// IRQ Mode + Irq = 0b10010, + /// Supervisor Mode + Svc = 0b10011, + /// Monitor Mode + Mon = 0b10110, + /// Abort Mode + Abt = 0b10111, + /// Hyp Mode + Hyp = 0b11010, + /// Undefined Mode + Und = 0b11011, + /// System Mode + Sys = 0b11111, +} + +/// The *Current Program Status Register* (CPSR) +#[bitbybit::bitfield(u32)] +pub struct Cpsr { + /// Negative Result from ALU + #[bits(31..=31, r)] + n: bool, + /// Zero Result from ALU + #[bits(30..=30, r)] + z: bool, + /// ALU operation Carry Out + #[bits(29..=29, r)] + c: bool, + /// ALU operation Overflow + #[bits(28..=28, r)] + v: bool, + /// Cumulative Saturation + #[bits(27..=27, r)] + q: bool, + /// Jazelle State + #[bits(24..=24, r)] + j: bool, + /// Endianness + #[bits(9..=9, rw)] + e: bool, + /// Asynchronous Aborts + #[bits(8..=8, rw)] + a: bool, + /// Interrupts Enabled + #[bits(7..=7, rw)] + i: bool, + /// Fast Interrupts Enabled + #[bits(6..=6, rw)] + f: bool, + /// Thumb state + #[bits(5..=5, rw)] + t: bool, + /// Processor Mode + #[bits(0..=4, rw)] + mode: Option, +} + +impl Cpsr { + /// Reads the *Current Program Status Register* + #[inline] + pub fn read() -> Self { + let r: u32; + // Safety: Reading this register has no side-effects and is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrs {}, CPSR", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self::new_with_raw_value(r) + } +} + +impl core::fmt::Debug for Cpsr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "CPSR {{ N={} Z={} C={} V={} Q={} J={} E={} A={} I={} F={} T={} MODE={:?} }}", + self.n() as u8, + self.z() as u8, + self.c() as u8, + self.v() as u8, + self.q() as u8, + self.j() as u8, + self.e() as u8, + self.a() as u8, + self.i() as u8, + self.f() as u8, + self.t() as u8, + self.mode(), + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Cpsr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "CPSR {{ N={0=31..32} Z={0=30..31} C={0=29..30} V={0=28..29} Q={0=27..28} J={0=24..25} E={0=9..10} A={0=8..9} I={0=7..8} F={0=6..7} T={0=5..6} MODE={0=0..5} }}", self.0) + } +} diff --git a/cortex-r/src/register/midr.rs b/cortex-r/src/register/midr.rs new file mode 100644 index 0000000..a0bc217 --- /dev/null +++ b/cortex-r/src/register/midr.rs @@ -0,0 +1,55 @@ +//! Code for managing the *Main ID Register* + +use arbitrary_int::{u12, u4}; + +/// The *Main ID Register* (MIDR) +#[bitbybit::bitfield(u32)] +pub struct Midr { + /// Implementer + #[bits(24..=31, r)] + implementer: u8, + /// Variant + #[bits(20..=23, r)] + variant: u4, + /// Architecture + #[bits(16..=19, r)] + arch: u4, + /// Part Number + #[bits(4..=15, r)] + part_no: u12, + /// Revision + #[bits(0..=3, r)] + rev: u4, +} + +impl Midr { + /// Reads the *Main ID Register* + #[inline] + pub fn read() -> Midr { + let r: u32; + // Safety: Reading this register has no side-effects and is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrc p15, 0, {}, c0, c0, 0", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self::new_with_raw_value(r) + } +} + +impl core::fmt::Debug for Midr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "MIDR {{ implementer=0x{:02x} variant=0x{:x} arch=0x{:x} part_no=0x{:03x} rev=0x{:x} }}", + self.implementer(), self.variant(), self.arch(), self.part_no(), self.rev()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Midr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "MIDR {{ implementer=0x{0=24..32:02x} variant=0x{0=20..24:x} arch=0x{0=16..20:x} part_no=0x{0=4..16:03x} rev=0x{0=0..4:x} }}", self.0) + } +} diff --git a/cortex-r/src/register/mod.rs b/cortex-r/src/register/mod.rs new file mode 100644 index 0000000..9767371 --- /dev/null +++ b/cortex-r/src/register/mod.rs @@ -0,0 +1,51 @@ +//! Access registers in Armv7-R and Armv8-R + +pub mod cpsr; +#[doc(inline)] +pub use cpsr::Cpsr; + +mod midr; +#[doc(inline)] +pub use midr::Midr; + +mod sctlr; +#[doc(inline)] +pub use sctlr::Sctlr; + +#[cfg(arm_architecture = "v8-r")] +mod armv8r; +#[doc(inline)] +#[cfg(arm_architecture = "v8-r")] +pub use armv8r::*; + +// TODO: + +// Multiprocessor Affinity Register (MPIDR) + +// System Control Register + +// Auxilliary Control Register + +// Coprocessor Access Control Register + +// Data Fault Status Register + +// Instruction Fault Status Register + +// Data Fault Address Register + +// Instruction Fault Address Register + +// MPU Region Base Address Register + +// MPU Region Size and Enable Register + +// MPU Region Access Control Register + +// MPU Region Number Register + +// Context ID Register + +// Software Thread ID Register + +// Configuration Base Address Register diff --git a/cortex-r/src/register/sctlr.rs b/cortex-r/src/register/sctlr.rs new file mode 100644 index 0000000..231e0b8 --- /dev/null +++ b/cortex-r/src/register/sctlr.rs @@ -0,0 +1,125 @@ +//! Code for managing the *System Control Register* + +/// The *System Control Register* (SCTLR) +#[bitbybit::bitfield(u32)] +pub struct Sctlr { + /// The bitmask for the Instruction Endianness bit + #[bits(31..=31, rw)] + ie: bool, + /// The bitmask for the Thumb Exception Enable bit + #[bits(30..=30, rw)] + te: bool, + /// The bitmask for the Non-Maskable FIQ bit + #[bits(27..=27, rw)] + nmfi: bool, + /// The bitmask for the Exception Endianness bit + #[bits(25..=25, rw)] + ee: bool, + /// The bitmask for the U bit + #[bits(22..=22, rw)] + u: bool, + /// The bitmask for the Fast Interrupt bit + #[bits(21..=21, rw)] + fi: bool, + /// The bitmask for the Divide by Zero Fault bit + #[bits(18..=18, rw)] + dz: bool, + /// The bitmask for the Background Region bit + #[bits(17..=17, rw)] + br: bool, + /// The bitmask for the Round Robin bit + #[bits(14..=14, rw)] + rr: bool, + /// The bitmask for the Exception Vector Table bit + #[bits(13..=13, rw)] + v: bool, + /// The bitmask for the Instruction Cache enable bit + #[bits(12..=12, rw)] + i: bool, + /// The bitmask for the Branch Prediction enable bit + #[bits(11..=11, rw)] + z: bool, + /// The bitmask for the SWP bit + #[bits(10..=10, rw)] + sw: bool, + /// The bitmask for the Cache enable bit + #[bits(2..=2, rw)] + c: bool, + /// The bitmask for the Alignment check bit + #[bits(1..=1, rw)] + a: bool, + /// The bitmask for the MPU bit + #[bits(0..=0, rw)] + m: bool, +} + +impl Sctlr { + /// Reads the *System Control Register* + #[inline] + pub fn read() -> Self { + let r: u32; + // Safety: Reading this register has no side-effects and is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mrc p15, 0, {}, c1, c0, 0", out(reg) r, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(target_arch = "arm"))] + { + r = 0; + } + Self::new_with_raw_value(r) + } + + /// Write to the *System Control Register* + #[inline] + pub fn write(_value: Self) { + // Safety: Writing this register is atomic + #[cfg(target_arch = "arm")] + unsafe { + core::arch::asm!("mcr p15, 0, {}, c1, c0, 0", in(reg) _value.raw_value(), options(nomem, nostack, preserves_flags)); + }; + } + + /// Modify the *System Control Register* + #[inline] + pub fn modify(f: F) + where + F: FnOnce(&mut Self), + { + let mut value = Self::read(); + f(&mut value); + Self::write(value); + } +} + +impl core::fmt::Debug for Sctlr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "SCTLR {{ IE={} TE={} NMFI={} EE={} U={} FI={} DZ={} BR={} RR={} V={} I={} Z={} SW={} C={} A={} M={} }}", + self.ie() as u8, + self.te() as u8, + self.nmfi() as u8, + self.ee() as u8, + self.u() as u8, + self.fi() as u8, + self.dz() as u8, + self.br() as u8, + self.rr() as u8, + self.v() as u8, + self.i() as u8, + self.z() as u8, + self.sw() as u8, + self.c() as u8, + self.a() as u8, + self.m() as u8, + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Sctlr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "SCTLR {{ IE={0=31..32} TE={0=30..31} NMFI={0=27..28} EE={0=25..26} U={0=22..23} FI={0=21..22} DZ={0=18..19} BR={0=17..18} RR={0=14..15} V={0=13..14} I={0=12..13} Z={0=11..12} SW={0=10..11} C={0=2..3} A={0=1..2} M={0=0..1} }}", self.0) + } +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 24d3186..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Low level access to Cortex-R processors -//! -//! # Supported Rust version -//! -//! Rust >=1.31.0 - -#![deny(missing_docs)] -#![deny(warnings)] -#![no_std] diff --git a/tests.sh b/tests.sh new file mode 100755 index 0000000..463aa7f --- /dev/null +++ b/tests.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Runs a series of sample programs in QEMU and checks that the standard output +# is as expected. + +rustup target add armv7r-none-eabi +rustup target add armv7r-none-eabihf + +FAILURE=0 + +fail() { + echo "***************************************************" + echo "MISMATCH: Binary $1 for target $2 mismatched" + echo "***************************************************" + FAILURE=1 +} + +mkdir -p ./target + +# armv7r-none-eabi tests +for binary in hello registers svc; do + cargo run --target=armv7r-none-eabi --bin $binary | tee ./target/$binary-armv7r-none-eabi.out + diff ./cortex-r-examples/reference/$binary-armv7r-none-eabi.out ./target/$binary-armv7r-none-eabi.out || fail $binary "armv7r-none-eabi" +done + +# armv7r-none-eabihf tests +for binary in hello registers svc; do + cargo run --target=armv7r-none-eabihf --bin $binary | tee ./target/$binary-armv7r-none-eabihf.out + diff ./cortex-r-examples/reference/$binary-armv7r-none-eabihf.out ./target/$binary-armv7r-none-eabihf.out || fail $binary "armv7r-none-eabihf" +done + +# Ubuntu 24.04 supplies QEMU 8, which doesn't support the machine we have configured for this target +# # armv8r-none-eabihf tests +# for binary in hello registers svc gic; do +# cargo +nightly run --target=armv8r-none-eabihf --bin $binary --features=gic -Zbuild-std=core | tee ./target/$binary-armv8r-none-eabihf.out +# diff ./cortex-r-examples/reference/$binary-armv8r-none-eabihf.out ./target/$binary-armv8r-none-eabihf.out || fail $binary "armv8r-none-eabihf" +# done + +if [ "$FAILURE" == "1" ]; then + echo "Output comparison failed!" + exit 1 +else + echo "Everything matches :)" +fi