├── .github ├── CODEOWNERS └── workflows │ ├── build.yaml │ ├── changelog.yaml │ ├── clippy.yaml │ ├── label.yaml │ └── rustfmt.yaml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── README.md ├── build.rs ├── examples ├── device.x ├── empty.rs └── multi_core.rs ├── link.x.in ├── macros ├── Cargo.toml └── src │ └── lib.rs └── src ├── asm.rs └── lib.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rust-embedded/riscv -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | merge_group: 6 | 7 | name: Build check 8 | 9 | jobs: 10 | build-riscv: 11 | strategy: 12 | matrix: 13 | # All generated code should be running on stable now, MRSV is 1.59.0 14 | toolchain: [ stable, nightly, 1.59.0 ] 15 | target: 16 | - riscv32i-unknown-none-elf 17 | - riscv32imc-unknown-none-elf 18 | - riscv32imac-unknown-none-elf 19 | - riscv64imac-unknown-none-elf 20 | - riscv64gc-unknown-none-elf 21 | example: 22 | - empty 23 | - multi_core 24 | cargo_flags: [ "--no-default-features", "--all-features" ] 25 | include: 26 | # Nightly is only for reference and allowed to fail 27 | - toolchain: nightly 28 | experimental: true 29 | runs-on: ubuntu-latest 30 | continue-on-error: ${{ matrix.experimental || false }} 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.toolchain }} 36 | targets: ${{ matrix.target }} 37 | - name: Build library 38 | run: cargo build --target ${{ matrix.target }} ${{ matrix.cargo_flags }} 39 | - name: Build example 40 | run: RUSTFLAGS="-C link-arg=-Texamples/device.x" cargo build --target ${{ matrix.target }} --example ${{ matrix.example }} ${{ matrix.cargo_flags }} 41 | 42 | # Job to check that all the builds succeeded 43 | build-check: 44 | needs: 45 | - build-riscv 46 | runs-on: ubuntu-latest 47 | if: always() 48 | steps: 49 | - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' 50 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yaml: -------------------------------------------------------------------------------- 1 | name: Check CHANGELOG.md 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 7 | 8 | jobs: 9 | changelog-check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | 15 | - name: Check for CHANGELOG.md 16 | uses: dangoslen/changelog-enforcer@v3 17 | with: 18 | skipLabels: 'skip changelog' 19 | missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | merge_group: 6 | 7 | name: Lints compliance check 8 | 9 | env: 10 | CLIPPY_PARAMS: -W clippy::all -W clippy::pedantic -W clippy::nursery -W clippy::cargo 11 | 12 | jobs: 13 | clippy: 14 | strategy: 15 | matrix: 16 | toolchain: [ stable, nightly ] 17 | cargo_flags: 18 | - "--no-default-features" 19 | - "--all-features" 20 | include: 21 | # Nightly is only for reference and allowed to fail 22 | - toolchain: nightly 23 | experimental: true 24 | runs-on: ubuntu-latest 25 | continue-on-error: ${{ matrix.experimental || false }} 26 | steps: 27 | - uses: actions/checkout@v3 28 | - uses: dtolnay/rust-toolchain@master 29 | with: 30 | toolchain: ${{ matrix.toolchain }} 31 | components: clippy 32 | - name: Run clippy 33 | run: cargo clippy --all ${{ matrix.cargo_flags }} -- -D warnings 34 | 35 | # Job to check that all the lint checks succeeded 36 | clippy-check: 37 | needs: 38 | - clippy 39 | runs-on: ubuntu-latest 40 | if: always() 41 | steps: 42 | - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' 43 | -------------------------------------------------------------------------------- /.github/workflows/label.yaml: -------------------------------------------------------------------------------- 1 | name: Check Labels 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 6 | 7 | jobs: 8 | label-check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: mheap/github-action-required-labels@v5 12 | with: 13 | mode: exactly 14 | count: 0 15 | labels: "work in progress, do not merge" 16 | add_comment: true 17 | message: "This PR is being prevented from merging because it presents one of the blocking labels: {{ provided }}." 18 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yaml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | merge_group: 7 | 8 | name: Code formatting check 9 | 10 | jobs: 11 | rustfmt: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: dtolnay/rust-toolchain@stable 16 | with: 17 | components: rustfmt 18 | - name: Run Rustfmt 19 | run: cargo fmt --all -- --check --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | bin/*.after 4 | bin/*.before 5 | bin/*.o 6 | 7 | .vscode/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | - New GitHub workflow for checking invalid labels in PRs 13 | - New GitHub workflow for checking modifications on CHANGELOG.md 14 | - New GitHub workflow for checking clippy lints in PRs 15 | - Optional cargo feature `single-hart` for single CPU targets 16 | 17 | ### Changed 18 | 19 | - moved to `riscv` repository. `riscv-rt` is now deprecated 20 | - Use inline assembly instead of pre-compiled blobs 21 | - Removed bors in favor of GitHub Merge Queue 22 | - `start_trap_rust` is now marked as `unsafe` 23 | - Implement `r0` as inline assembly 24 | - Use `${ARCH_WIDTH}` in `link.x.in` to adapt to different archs 25 | - mhartid CSR is no longer read in single-hart mode, assumed zero 26 | - Ensure stack pointer is 16-byte aligned before jumping to Rust entry point 27 | 28 | ## [v0.11.0] - 2023-01-18 29 | 30 | ### Changed 31 | 32 | - Update `riscv` to version 0.10.1 fixing a critical section bug 33 | 34 | ## [v0.10.0] - 2022-11-04 35 | 36 | ### Added 37 | 38 | - Optional cargo feature `s-mode` for supervisor mode, including conditional compilation for supervisor/machine mode instructions. 39 | 40 | ### Changed 41 | 42 | - Remove superfluous parentheses from link.x, which caused linker errors with nightly. 43 | - Changed `mp_hook` signature, hartid as passed as usize parameter by the caller (required for `s-mode` feature). 44 | - Update `riscv` to version 0.9 45 | 46 | ## [v0.9.0] - 2022-07-01 47 | 48 | ### Added 49 | 50 | - Pass `a0..a2` register values to the `#[entry]` function. 51 | 52 | ### Changed 53 | 54 | - Update `riscv` to version 0.8 55 | - Update `riscv-rt-macros` to 0.2.0 56 | - Update Minimum Supported Rust Version to 1.59 57 | - The main symbol is no longer randomly generated in the `#[entry]` macro, instead it uses `__risc_v_rt__main`. 58 | 59 | ### Removed 60 | 61 | - Remove `inline-asm` feature which is now always enabled 62 | 63 | ## [v0.8.1] - 2022-01-25 64 | 65 | ### Added 66 | 67 | - Enable float support for targets with extension sets F and D 68 | - Add ability to override trap handling mechanism 69 | 70 | ### Changed 71 | 72 | - Update `riscv` to version 0.7 73 | - Update `quote` to version 1.0 74 | - Update `proc-macro2` to version 1.0 75 | - Update `rand` to version to version 0.7.3 76 | 77 | ## [v0.8.0] - 2020-07-18 78 | 79 | ### Changed 80 | 81 | - Update `riscv` to version 0.6 82 | - Update Minimum Supported Rust Version to 1.42.0 83 | 84 | ## [v0.7.2] - 2020-07-16 85 | 86 | ### Changed 87 | 88 | - Preserve `.eh_frame` and `.eh_frame_hdr` sections 89 | - Place `.srodata` and `.srodata.*` sections in `.rodata` 90 | 91 | ## [v0.7.1] - 2020-06-02 92 | 93 | ### Added 94 | 95 | - Add support to initialize custom interrupt controllers. 96 | 97 | ### Changed 98 | 99 | - Exception handler may return now 100 | 101 | ## [v0.7.0] - 2020-03-10 102 | 103 | ### Added 104 | 105 | - Assure address of PC at startup 106 | - Implement interrupt and exception handling 107 | - Add support for the `riscv32i-unknown-none-elf` target 108 | - Added Changelog 109 | 110 | ### Fixed 111 | 112 | - Fix linker script compatibility with GNU linker 113 | 114 | ### Changed 115 | 116 | - Move `abort` out of the `.init` section 117 | - Update `r0` to v1.0.0 118 | - Set MSRV to 1.38 119 | 120 | 121 | [Unreleased]: https://github.com/rust-embedded/riscv-rt/compare/v0.11.0..HEAD 122 | [v0.10.1]: https://github.com/rust-embedded/riscv-rt/compare/v0.10.0...v0.11.0 123 | [v0.10.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.9.1...v0.10.0 124 | [v0.9.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.8.1...v0.9.0 125 | [v0.8.1]: https://github.com/rust-embedded/riscv-rt/compare/v0.8.0...v0.8.1 126 | [v0.8.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.7.2...v0.8.0 127 | [v0.7.2]: https://github.com/rust-embedded/riscv-rt/compare/v0.7.1...v0.7.2 128 | [v0.7.1]: https://github.com/rust-embedded/riscv-rt/compare/v0.7.0...v0.7.1 129 | [v0.7.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.6.1...v0.7.0 130 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The Rust Code of Conduct 2 | 3 | ## Conduct 4 | 5 | **Contact**: [RISC-V team](https://github.com/rust-embedded/wg#the-riscv-team) 6 | 7 | * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. 8 | * On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. 9 | * Please be kind and courteous. There's no need to be mean or rude. 10 | * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. 11 | * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. 12 | * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. 13 | * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [RISC-V team][team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. 14 | * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. 15 | 16 | ## Moderation 17 | 18 | These are the policies for upholding our community's standards of conduct. 19 | 20 | 1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) 21 | 2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. 22 | 3. Moderators will first respond to such remarks with a warning. 23 | 4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. 24 | 5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. 25 | 6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. 26 | 7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. 27 | 8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. 28 | 29 | In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. 30 | 31 | And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. 32 | 33 | The enforcement policies listed above apply to all official embedded WG venues; including official IRC channels (#rust-embedded); GitHub repositories under rust-embedded; and all forums under rust-embedded.org (forum.rust-embedded.org). 34 | 35 | *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* 36 | 37 | [team]: https://github.com/rust-embedded/wg#the-riscv-team 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "riscv-rt" 3 | version = "0.11.0" 4 | rust-version = "1.59" 5 | repository = "https://github.com/rust-embedded/riscv-rt" 6 | authors = ["The RISC-V Team "] 7 | categories = ["embedded", "no-std"] 8 | description = "Minimal runtime / startup for RISC-V CPU's" 9 | keywords = ["riscv", "runtime", "startup"] 10 | license = "ISC" 11 | edition = "2018" 12 | 13 | [features] 14 | s-mode = [] 15 | single-hart = [] 16 | 17 | [dependencies] 18 | riscv = "0.10" 19 | riscv-rt-macros = { path = "macros", version = "0.2.0" } 20 | 21 | [dev-dependencies] 22 | panic-halt = "0.2.0" 23 | 24 | [build-dependencies] 25 | riscv-target = "0.1.2" 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/d/riscv-rt.svg)](https://crates.io/crates/riscv-rt) 2 | [![crates.io](https://img.shields.io/crates/v/riscv-rt.svg)](https://crates.io/crates/riscv-rt) 3 | [![Build Status](https://travis-ci.org/rust-embedded/riscv-rt.svg?branch=master)](https://travis-ci.org/rust-embedded/riscv-rt) 4 | 5 | # `riscv-rt` 6 | 7 | > Minimal runtime / startup for RISC-V CPU's. 8 | 9 | **This crate has moved to the [`riscv`] repository, and this repository is 10 | archived. Please direct issues and pull requests to the new repository.** 11 | 12 | [`riscv`]: https://github.com/rust-embedded/riscv 13 | 14 | This project is developed and maintained by the [RISC-V team][team]. 15 | 16 | ## [Documentation](https://docs.rs/crate/riscv-rt) 17 | 18 | ## Minimum Supported Rust Version (MSRV) 19 | 20 | This crate is guaranteed to compile on stable Rust 1.59 and up. It *might* 21 | compile with older versions but that may change in any new patch release. 22 | 23 | ## License 24 | 25 | Copyright 2018-2022 [RISC-V team][team] 26 | 27 | Permission to use, copy, modify, and/or distribute this software for any purpose 28 | with or without fee is hereby granted, provided that the above copyright notice 29 | and this permission notice appear in all copies. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 32 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 33 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 34 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 35 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 36 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 37 | THIS SOFTWARE. 38 | 39 | ## Code of Conduct 40 | 41 | Contribution to this crate is organized under the terms of the [Rust Code of 42 | Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises 43 | to intervene to uphold that code of conduct. 44 | 45 | [CoC]: CODE_OF_CONDUCT.md 46 | [team]: https://github.com/rust-embedded/wg#the-risc-v-team 47 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // NOTE: Adapted from cortex-m/build.rs 2 | 3 | use riscv_target::Target; 4 | use std::{env, fs, io, path::PathBuf}; 5 | 6 | fn add_linker_script(arch_width: u32) -> io::Result<()> { 7 | // Read the file to a string and replace all occurrences of ${ARCH_WIDTH} with the arch width 8 | let mut content = fs::read_to_string("link.x.in")?; 9 | content = content.replace("${ARCH_WIDTH}", &arch_width.to_string()); 10 | 11 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 12 | 13 | // Put the linker script somewhere the linker can find it 14 | fs::write(out_dir.join("link.x"), content)?; 15 | println!("cargo:rustc-link-search={}", out_dir.display()); 16 | println!("cargo:rerun-if-changed=link.x"); 17 | 18 | Ok(()) 19 | } 20 | 21 | fn main() { 22 | let target = env::var("TARGET").unwrap(); 23 | let _name = env::var("CARGO_PKG_NAME").unwrap(); 24 | 25 | // set configuration flags depending on the target 26 | if target.starts_with("riscv") { 27 | println!("cargo:rustc-cfg=riscv"); 28 | let target = Target::from_target_str(&target); 29 | 30 | // generate the linker script 31 | let arch_width = match target.bits { 32 | 32 => { 33 | println!("cargo:rustc-cfg=riscv32"); 34 | 4 35 | } 36 | 64 => { 37 | println!("cargo:rustc-cfg=riscv64"); 38 | 8 39 | } 40 | _ => panic!("Unsupported bit width"), 41 | }; 42 | add_linker_script(arch_width).unwrap(); 43 | 44 | // expose the ISA extensions 45 | if target.has_extension('m') { 46 | println!("cargo:rustc-cfg=riscvm"); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/device.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | RAM : ORIGIN = 0x80000000, LENGTH = 16K 4 | FLASH : ORIGIN = 0x20000000, LENGTH = 4M 5 | } 6 | 7 | REGION_ALIAS("REGION_TEXT", FLASH); 8 | REGION_ALIAS("REGION_RODATA", FLASH); 9 | REGION_ALIAS("REGION_DATA", RAM); 10 | REGION_ALIAS("REGION_BSS", RAM); 11 | REGION_ALIAS("REGION_HEAP", RAM); 12 | REGION_ALIAS("REGION_STACK", RAM); 13 | 14 | INCLUDE link.x 15 | -------------------------------------------------------------------------------- /examples/empty.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate panic_halt; 5 | extern crate riscv_rt; 6 | 7 | use riscv_rt::entry; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | // do something here 12 | loop {} 13 | } 14 | -------------------------------------------------------------------------------- /examples/multi_core.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate panic_halt; 5 | extern crate riscv; 6 | extern crate riscv_rt; 7 | 8 | use riscv::asm::wfi; 9 | use riscv::register::{mie, mip}; 10 | use riscv_rt::entry; 11 | 12 | #[export_name = "_mp_hook"] 13 | #[rustfmt::skip] 14 | pub extern "Rust" fn user_mp_hook(hartid: usize) -> bool { 15 | if hartid == 0 { 16 | true 17 | } else { 18 | let addr = 0x02000000 + hartid * 4; 19 | unsafe { 20 | // Clear IPI 21 | (addr as *mut u32).write_volatile(0); 22 | 23 | // Start listening for software interrupts 24 | mie::set_msoft(); 25 | 26 | loop { 27 | wfi(); 28 | if mip::read().msoft() { 29 | break; 30 | } 31 | } 32 | 33 | // Stop listening for software interrupts 34 | mie::clear_msoft(); 35 | 36 | // Clear IPI 37 | (addr as *mut u32).write_volatile(0); 38 | } 39 | false 40 | } 41 | } 42 | 43 | #[entry] 44 | fn main(hartid: usize) -> ! { 45 | if hartid == 0 { 46 | // Waking hart 1... 47 | let addr = 0x02000004; 48 | unsafe { 49 | (addr as *mut u32).write_volatile(1); 50 | } 51 | } 52 | 53 | loop {} 54 | } 55 | -------------------------------------------------------------------------------- /link.x.in: -------------------------------------------------------------------------------- 1 | /* # Developer notes 2 | 3 | - Symbols that start with a double underscore (__) are considered "private" 4 | 5 | - Symbols that start with a single underscore (_) are considered "semi-public"; they can be 6 | overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { 7 | static mut _heap_size }`). 8 | 9 | - `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a 10 | symbol if not dropped if it appears in or near the front of the linker arguments and "it's not 11 | needed" by any of the preceding objects (linker arguments) 12 | 13 | - `PROVIDE` is used to provide default values that can be overridden by a user linker script 14 | 15 | - In this linker script, you may find symbols that look like `${...}` (e.g., `${ARCH_WIDTH}`). 16 | These are wildcards used by the `build.rs` script to adapt to different target particularities. 17 | Check `build.rs` for more details about these symbols. 18 | 19 | - On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* 20 | the LMA of .data are all `${ARCH_WIDTH}`-byte aligned. These alignments are assumed by the RAM 21 | initialization routine. There's also a second benefit: `${ARCH_WIDTH}`-byte aligned boundaries 22 | means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. 23 | */ 24 | 25 | PROVIDE(_stext = ORIGIN(REGION_TEXT)); 26 | PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)); 27 | PROVIDE(_max_hart_id = 0); 28 | PROVIDE(_hart_stack_size = 2K); 29 | PROVIDE(_heap_size = 0); 30 | 31 | PROVIDE(UserSoft = DefaultHandler); 32 | PROVIDE(SupervisorSoft = DefaultHandler); 33 | PROVIDE(MachineSoft = DefaultHandler); 34 | PROVIDE(UserTimer = DefaultHandler); 35 | PROVIDE(SupervisorTimer = DefaultHandler); 36 | PROVIDE(MachineTimer = DefaultHandler); 37 | PROVIDE(UserExternal = DefaultHandler); 38 | PROVIDE(SupervisorExternal = DefaultHandler); 39 | PROVIDE(MachineExternal = DefaultHandler); 40 | 41 | PROVIDE(DefaultHandler = DefaultInterruptHandler); 42 | PROVIDE(ExceptionHandler = DefaultExceptionHandler); 43 | 44 | /* # Pre-initialization function */ 45 | /* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, 46 | then the function this points to will be called before the RAM is initialized. */ 47 | PROVIDE(__pre_init = default_pre_init); 48 | 49 | /* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ 50 | PROVIDE(_setup_interrupts = default_setup_interrupts); 51 | 52 | /* # Multi-processing hook function 53 | fn _mp_hook() -> bool; 54 | 55 | This function is called from all the harts and must return true only for one hart, 56 | which will perform memory initialization. For other harts it must return false 57 | and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). 58 | */ 59 | PROVIDE(_mp_hook = default_mp_hook); 60 | 61 | /* # Start trap function override 62 | By default uses the riscv crates default trap handler 63 | but by providing the `_start_trap` symbol external crates can override. 64 | */ 65 | PROVIDE(_start_trap = default_start_trap); 66 | 67 | SECTIONS 68 | { 69 | .text.dummy (NOLOAD) : 70 | { 71 | /* This section is intended to make _stext address work */ 72 | . = ABSOLUTE(_stext); 73 | } > REGION_TEXT 74 | 75 | .text _stext : 76 | { 77 | /* Put reset handler first in .text section so it ends up as the entry */ 78 | /* point of the program. */ 79 | KEEP(*(.init)); 80 | KEEP(*(.init.rust)); 81 | . = ALIGN(4); 82 | *(.trap); 83 | *(.trap.rust); 84 | *(.text.abort); 85 | *(.text .text.*); 86 | } > REGION_TEXT 87 | 88 | .rodata : ALIGN(4) 89 | { 90 | *(.srodata .srodata.*); 91 | *(.rodata .rodata.*); 92 | 93 | /* 4-byte align the end (VMA) of this section. 94 | This is required by LLD to ensure the LMA of the following .data 95 | section will have the correct alignment. */ 96 | . = ALIGN(4); 97 | } > REGION_RODATA 98 | 99 | .data : ALIGN(${ARCH_WIDTH}) 100 | { 101 | _sidata = LOADADDR(.data); 102 | _sdata = .; 103 | /* Must be called __global_pointer$ for linker relaxations to work. */ 104 | PROVIDE(__global_pointer$ = . + 0x800); 105 | *(.sdata .sdata.* .sdata2 .sdata2.*); 106 | *(.data .data.*); 107 | . = ALIGN(${ARCH_WIDTH}); 108 | _edata = .; 109 | } > REGION_DATA AT > REGION_RODATA 110 | 111 | .bss (NOLOAD) : ALIGN(${ARCH_WIDTH}) 112 | { 113 | _sbss = .; 114 | *(.sbss .sbss.* .bss .bss.*); 115 | . = ALIGN(${ARCH_WIDTH}); 116 | _ebss = .; 117 | } > REGION_BSS 118 | 119 | /* fictitious region that represents the memory available for the heap */ 120 | .heap (NOLOAD) : 121 | { 122 | _sheap = .; 123 | . += _heap_size; 124 | . = ALIGN(4); 125 | _eheap = .; 126 | } > REGION_HEAP 127 | 128 | /* fictitious region that represents the memory available for the stack */ 129 | .stack (NOLOAD) : 130 | { 131 | _estack = .; 132 | . = ABSOLUTE(_stack_start); 133 | _sstack = .; 134 | } > REGION_STACK 135 | 136 | /* fake output .got section */ 137 | /* Dynamic relocations are unsupported. This section is only used to detect 138 | relocatable code in the input files and raise an error if relocatable code 139 | is found */ 140 | .got (INFO) : 141 | { 142 | KEEP(*(.got .got.*)); 143 | } 144 | 145 | .eh_frame (INFO) : { KEEP(*(.eh_frame)) } 146 | .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } 147 | } 148 | 149 | /* Do not exceed this mark in the error messages above | */ 150 | ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " 151 | ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); 152 | 153 | ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, " 154 | ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned"); 155 | 156 | ASSERT(ORIGIN(REGION_DATA) % ${ARCH_WIDTH} == 0, " 157 | ERROR(riscv-rt): the start of the REGION_DATA must be ${ARCH_WIDTH}-byte aligned"); 158 | 159 | ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " 160 | ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); 161 | 162 | ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " 163 | ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); 164 | 165 | ASSERT(ORIGIN(REGION_STACK) % 4 == 0, " 166 | ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned"); 167 | 168 | ASSERT(_stext % 4 == 0, " 169 | ERROR(riscv-rt): `_stext` must be 4-byte aligned"); 170 | 171 | ASSERT(_sdata % ${ARCH_WIDTH} == 0 && _edata % ${ARCH_WIDTH} == 0, " 172 | BUG(riscv-rt): .data is not ${ARCH_WIDTH}-byte aligned"); 173 | 174 | ASSERT(_sidata % ${ARCH_WIDTH} == 0, " 175 | BUG(riscv-rt): the LMA of .data is not ${ARCH_WIDTH}-byte aligned"); 176 | 177 | ASSERT(_sbss % ${ARCH_WIDTH} == 0 && _ebss % ${ARCH_WIDTH} == 0, " 178 | BUG(riscv-rt): .bss is not ${ARCH_WIDTH}-byte aligned"); 179 | 180 | ASSERT(_sheap % 4 == 0, " 181 | BUG(riscv-rt): start of .heap is not 4-byte aligned"); 182 | 183 | ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), " 184 | ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region. 185 | Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'"); 186 | 187 | ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " 188 | ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. 189 | Consider changing `_max_hart_id` or `_hart_stack_size`."); 190 | 191 | ASSERT(SIZEOF(.got) == 0, " 192 | .got section detected in the input files. Dynamic relocations are not 193 | supported. If you are linking to C code compiled using the `gcc` crate 194 | then modify your build script to compile the C code _without_ the 195 | -fPIC flag. See the documentation of the `gcc::Config.fpic` method for 196 | details."); 197 | 198 | /* Do not exceed this mark in the error messages above | */ 199 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "The RISC-V Team ", 4 | "Jorge Aparicio ", 5 | ] 6 | categories = ["embedded", "no-std"] 7 | description = "Attributes re-exported in `riscv-rt`" 8 | documentation = "https://docs.rs/riscv-rt" 9 | keywords = ["riscv", "runtime", "startup"] 10 | license = "MIT OR Apache-2.0" 11 | name = "riscv-rt-macros" 12 | repository = "https://github.com/rust-embedded/riscv-rt" 13 | version = "0.2.0" 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | quote = "1.0" 20 | proc-macro2 = "1.0" 21 | 22 | [dependencies.syn] 23 | version = "1.0" 24 | features = ["extra-traits", "full"] 25 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | extern crate proc_macro; 4 | #[macro_use] 5 | extern crate quote; 6 | extern crate core; 7 | extern crate proc_macro2; 8 | #[macro_use] 9 | extern crate syn; 10 | 11 | use proc_macro2::Span; 12 | use syn::{parse, spanned::Spanned, FnArg, ItemFn, PathArguments, ReturnType, Type, Visibility}; 13 | 14 | use proc_macro::TokenStream; 15 | 16 | /// Attribute to declare the entry point of the program 17 | /// 18 | /// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you 19 | /// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no 20 | /// private modules between the item and the root of the crate); if the item is in the root of the 21 | /// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases. 22 | /// 23 | /// The specified function will be called by the reset handler *after* RAM has been initialized. 24 | /// If present, the FPU will also be enabled before the function is called. 25 | /// 26 | /// The type of the specified function must be `[unsafe] fn() -> !` (never ending function) 27 | /// 28 | /// # Properties 29 | /// 30 | /// The entry point will be called by the reset handler. The program can't reference to the entry 31 | /// point, much less invoke it. 32 | /// 33 | /// # Examples 34 | /// 35 | /// - Simple entry point 36 | /// 37 | /// ``` no_run 38 | /// # #![no_main] 39 | /// # use riscv_rt_macros::entry; 40 | /// #[entry] 41 | /// fn main() -> ! { 42 | /// loop { 43 | /// /* .. */ 44 | /// } 45 | /// } 46 | /// ``` 47 | #[proc_macro_attribute] 48 | pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { 49 | let f = parse_macro_input!(input as ItemFn); 50 | 51 | // check the function arguments 52 | if f.sig.inputs.len() > 3 { 53 | return parse::Error::new( 54 | f.sig.inputs.last().unwrap().span(), 55 | "`#[entry]` function has too many arguments", 56 | ) 57 | .to_compile_error() 58 | .into(); 59 | } 60 | for arg in &f.sig.inputs { 61 | match arg { 62 | FnArg::Receiver(_) => { 63 | return parse::Error::new(arg.span(), "invalid argument") 64 | .to_compile_error() 65 | .into(); 66 | } 67 | FnArg::Typed(t) => { 68 | if !is_simple_type(&t.ty, "usize") { 69 | return parse::Error::new(t.ty.span(), "argument type must be usize") 70 | .to_compile_error() 71 | .into(); 72 | } 73 | } 74 | } 75 | } 76 | 77 | // check the function signature 78 | let valid_signature = f.sig.constness.is_none() 79 | && f.sig.asyncness.is_none() 80 | && f.vis == Visibility::Inherited 81 | && f.sig.abi.is_none() 82 | && f.sig.generics.params.is_empty() 83 | && f.sig.generics.where_clause.is_none() 84 | && f.sig.variadic.is_none() 85 | && match f.sig.output { 86 | ReturnType::Default => false, 87 | ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), 88 | }; 89 | 90 | if !valid_signature { 91 | return parse::Error::new( 92 | f.span(), 93 | "`#[entry]` function must have signature `[unsafe] fn([arg0: usize, ...]) -> !`", 94 | ) 95 | .to_compile_error() 96 | .into(); 97 | } 98 | 99 | if !args.is_empty() { 100 | return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") 101 | .to_compile_error() 102 | .into(); 103 | } 104 | 105 | // XXX should we blacklist other attributes? 106 | let attrs = f.attrs; 107 | let unsafety = f.sig.unsafety; 108 | let args = f.sig.inputs; 109 | let stmts = f.block.stmts; 110 | 111 | quote!( 112 | #[allow(non_snake_case)] 113 | #[export_name = "main"] 114 | #(#attrs)* 115 | pub #unsafety fn __risc_v_rt__main(#args) -> ! { 116 | #(#stmts)* 117 | } 118 | ) 119 | .into() 120 | } 121 | 122 | #[allow(unused)] 123 | fn is_simple_type(ty: &Type, name: &str) -> bool { 124 | if let Type::Path(p) = ty { 125 | if p.qself.is_none() && p.path.leading_colon.is_none() && p.path.segments.len() == 1 { 126 | let segment = p.path.segments.first().unwrap(); 127 | if segment.ident == name && segment.arguments == PathArguments::None { 128 | return true; 129 | } 130 | } 131 | } 132 | false 133 | } 134 | 135 | /// Attribute to mark which function will be called at the beginning of the reset handler. 136 | /// 137 | /// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. Also, if you 138 | /// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no 139 | /// private modules between the item and the root of the crate); if the item is in the root of the 140 | /// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer 141 | /// releases. 142 | /// 143 | /// The function must have the signature of `unsafe fn()`. 144 | /// 145 | /// The function passed will be called before static variables are initialized. Any access of static 146 | /// variables will result in undefined behavior. 147 | /// 148 | /// # Examples 149 | /// 150 | /// ``` 151 | /// # use riscv_rt_macros::pre_init; 152 | /// #[pre_init] 153 | /// unsafe fn before_main() { 154 | /// // do something here 155 | /// } 156 | /// 157 | /// # fn main() {} 158 | /// ``` 159 | #[proc_macro_attribute] 160 | pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream { 161 | let f = parse_macro_input!(input as ItemFn); 162 | 163 | // check the function signature 164 | let valid_signature = f.sig.constness.is_none() 165 | && f.sig.asyncness.is_none() 166 | && f.vis == Visibility::Inherited 167 | && f.sig.unsafety.is_some() 168 | && f.sig.abi.is_none() 169 | && f.sig.inputs.is_empty() 170 | && f.sig.generics.params.is_empty() 171 | && f.sig.generics.where_clause.is_none() 172 | && f.sig.variadic.is_none() 173 | && match f.sig.output { 174 | ReturnType::Default => true, 175 | ReturnType::Type(_, ref ty) => match **ty { 176 | Type::Tuple(ref tuple) => tuple.elems.is_empty(), 177 | _ => false, 178 | }, 179 | }; 180 | 181 | if !valid_signature { 182 | return parse::Error::new( 183 | f.span(), 184 | "`#[pre_init]` function must have signature `unsafe fn()`", 185 | ) 186 | .to_compile_error() 187 | .into(); 188 | } 189 | 190 | if !args.is_empty() { 191 | return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") 192 | .to_compile_error() 193 | .into(); 194 | } 195 | 196 | // XXX should we blacklist other attributes? 197 | let attrs = f.attrs; 198 | let ident = f.sig.ident; 199 | let block = f.block; 200 | 201 | quote!( 202 | #[export_name = "__pre_init"] 203 | #(#attrs)* 204 | pub unsafe fn #ident() #block 205 | ) 206 | .into() 207 | } 208 | -------------------------------------------------------------------------------- /src/asm.rs: -------------------------------------------------------------------------------- 1 | use core::arch::global_asm; 2 | 3 | /// Parse cfg attributes inside a global_asm call. 4 | macro_rules! cfg_global_asm { 5 | {@inner, [$($x:tt)*], } => { 6 | global_asm!{$($x)*} 7 | }; 8 | (@inner, [$($x:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => { 9 | #[cfg($meta)] 10 | cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*} 11 | #[cfg(not($meta))] 12 | cfg_global_asm!{@inner, [$($x)*], $($rest)*} 13 | }; 14 | {@inner, [$($x:tt)*], $asm:literal, $($rest:tt)*} => { 15 | cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*} 16 | }; 17 | {$($asms:tt)*} => { 18 | cfg_global_asm!{@inner, [], $($asms)*} 19 | }; 20 | } 21 | 22 | // Entry point of all programs (_start). It initializes DWARF call frame information, 23 | // the stack pointer, the frame pointer (needed for closures to work in start_rust) 24 | // and the global pointer. Then it calls _start_rust. 25 | cfg_global_asm!( 26 | ".section .init, \"ax\" 27 | .global _start 28 | 29 | _start:", 30 | #[cfg(riscv32)] 31 | "lui ra, %hi(_abs_start) 32 | jr %lo(_abs_start)(ra)", 33 | #[cfg(riscv64)] 34 | ".option push 35 | .option norelax // to prevent an unsupported R_RISCV_ALIGN relocation from being generated 36 | 1: 37 | auipc ra, %pcrel_hi(1f) 38 | ld ra, %pcrel_lo(1b)(ra) 39 | jr ra 40 | .align 3 41 | 1: 42 | .dword _abs_start 43 | .option pop", 44 | " 45 | _abs_start: 46 | .option norelax 47 | .cfi_startproc 48 | .cfi_undefined ra", 49 | #[cfg(feature = "s-mode")] 50 | "csrw sie, 0 51 | csrw sip, 0", 52 | #[cfg(not(feature = "s-mode"))] 53 | "csrw mie, 0 54 | csrw mip, 0", 55 | "li x1, 0 56 | li x2, 0 57 | li x3, 0 58 | li x4, 0 59 | li x5, 0 60 | li x6, 0 61 | li x7, 0 62 | li x8, 0 63 | li x9, 0 64 | // a0..a2 (x10..x12) skipped 65 | li x13, 0 66 | li x14, 0 67 | li x15, 0 68 | li x16, 0 69 | li x17, 0 70 | li x18, 0 71 | li x19, 0 72 | li x20, 0 73 | li x21, 0 74 | li x22, 0 75 | li x23, 0 76 | li x24, 0 77 | li x25, 0 78 | li x26, 0 79 | li x27, 0 80 | li x28, 0 81 | li x29, 0 82 | li x30, 0 83 | li x31, 0 84 | 85 | .option push 86 | .option norelax 87 | la gp, __global_pointer$ 88 | .option pop 89 | // Allocate stacks", 90 | #[cfg(all(not(feature = "single-hart"), feature = "s-mode"))] 91 | "mv t2, a0 // the hartid is passed as parameter by SMODE", 92 | #[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))] 93 | "csrr t2, mhartid", 94 | #[cfg(not(feature = "single-hart"))] 95 | "lui t0, %hi(_max_hart_id) 96 | add t0, t0, %lo(_max_hart_id) 97 | bgtu t2, t0, abort 98 | lui t0, %hi(_hart_stack_size) 99 | add t0, t0, %lo(_hart_stack_size)", 100 | #[cfg(all(not(feature = "single-hart"), riscvm))] 101 | "mul t0, t2, t0", 102 | #[cfg(all(not(feature = "single-hart"), not(riscvm)))] 103 | "beqz t2, 2f // Jump if single-hart 104 | mv t1, t2 105 | mv t3, t0 106 | 1: 107 | add t0, t0, t3 108 | addi t1, t1, -1 109 | bnez t1, 1b 110 | 2: ", 111 | "la t1, _stack_start", 112 | #[cfg(not(feature = "single-hart"))] 113 | "sub t1, t1, t0", 114 | "andi sp, t1, -16 // Force 16-byte alignment 115 | // Set frame pointer 116 | add s0, sp, zero 117 | 118 | jal zero, _start_rust 119 | 120 | .cfi_endproc", 121 | ); 122 | 123 | /// Trap entry point (_start_trap). It saves caller saved registers, calls 124 | /// _start_trap_rust, restores caller saved registers and then returns. 125 | /// 126 | /// # Usage 127 | /// 128 | /// The macro takes 5 arguments: 129 | /// - `$STORE`: the instruction used to store a register in the stack (e.g. `sd` for riscv64) 130 | /// - `$LOAD`: the instruction used to load a register from the stack (e.g. `ld` for riscv64) 131 | /// - `$BYTES`: the number of bytes used to store a register (e.g. 8 for riscv64) 132 | /// - `$TRAP_SIZE`: the number of registers to store in the stack (e.g. 32 for all the user registers) 133 | /// - list of tuples of the form `($REG, $LOCATION)`, where: 134 | /// - `$REG`: the register to store/load 135 | /// - `$LOCATION`: the location in the stack where to store/load the register 136 | #[rustfmt::skip] 137 | macro_rules! trap_handler { 138 | ($STORE:ident, $LOAD:ident, $BYTES:literal, $TRAP_SIZE:literal, [$(($REG:ident, $LOCATION:literal)),*]) => { 139 | // ensure we do not break that sp is 16-byte aligned 140 | const _: () = assert!(($TRAP_SIZE * $BYTES) % 16 == 0); 141 | global_asm!( 142 | " 143 | .section .trap, \"ax\" 144 | .global default_start_trap 145 | default_start_trap:", 146 | // save space for trap handler in stack 147 | concat!("addi sp, sp, -", stringify!($TRAP_SIZE * $BYTES)), 148 | // save registers in the desired order 149 | $(concat!(stringify!($STORE), " ", stringify!($REG), ", ", stringify!($LOCATION * $BYTES), "(sp)"),)* 150 | // call rust trap handler 151 | "add a0, sp, zero 152 | jal ra, _start_trap_rust", 153 | // restore registers in the desired order 154 | $(concat!(stringify!($LOAD), " ", stringify!($REG), ", ", stringify!($LOCATION * $BYTES), "(sp)"),)* 155 | // free stack 156 | concat!("addi sp, sp, ", stringify!($TRAP_SIZE * $BYTES)), 157 | ); 158 | cfg_global_asm!( 159 | // return from trap 160 | #[cfg(feature = "s-mode")] 161 | "sret", 162 | #[cfg(not(feature = "s-mode"))] 163 | "mret", 164 | ); 165 | }; 166 | } 167 | 168 | #[rustfmt::skip] 169 | #[cfg(riscv32)] 170 | trap_handler!( 171 | sw, lw, 4, 16, 172 | [(ra, 0), (t0, 1), (t1, 2), (t2, 3), (t3, 4), (t4, 5), (t5, 6), (t6, 7), 173 | (a0, 8), (a1, 9), (a2, 10), (a3, 11), (a4, 12), (a5, 13), (a6, 14), (a7, 15)] 174 | ); 175 | #[rustfmt::skip] 176 | #[cfg(riscv64)] 177 | trap_handler!( 178 | sd, ld, 8, 16, 179 | [(ra, 0), (t0, 1), (t1, 2), (t2, 3), (t3, 4), (t4, 5), (t5, 6), (t6, 7), 180 | (a0, 8), (a1, 9), (a2, 10), (a3, 11), (a4, 12), (a5, 13), (a6, 14), (a7, 15)] 181 | ); 182 | 183 | // Make sure there is an abort when linking 184 | global_asm!( 185 | ".section .text.abort 186 | .globl abort 187 | abort: 188 | j abort" 189 | ); 190 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Minimal startup / runtime for RISC-V CPU's 2 | //! 3 | //! # Minimum Supported Rust Version (MSRV) 4 | //! 5 | //! This crate is guaranteed to compile on stable Rust 1.59 and up. It *might* 6 | //! compile with older versions but that may change in any new patch release. 7 | //! 8 | //! # Features 9 | //! 10 | //! This crate provides 11 | //! 12 | //! - Before main initialization of the `.bss` and `.data` sections. 13 | //! 14 | //! - `#[entry]` to declare the entry point of the program 15 | //! - `#[pre_init]` to run code *before* `static` variables are initialized 16 | //! 17 | //! - A linker script that encodes the memory layout of a generic RISC-V 18 | //! microcontroller. This linker script is missing some information that must 19 | //! be supplied through a `memory.x` file (see example below). This file 20 | //! must be supplied using rustflags and listed *before* `link.x`. Arbitrary 21 | //! filename can be use instead of `memory.x`. 22 | //! 23 | //! - A `_sheap` symbol at whose address you can locate a heap. 24 | //! 25 | //! - Support for a runtime in supervisor mode, that can be bootstrapped via [Supervisor Binary Interface (SBI)](https://github.com/riscv-non-isa/riscv-sbi-doc) 26 | //! 27 | //! ``` text 28 | //! $ cargo new --bin app && cd $_ 29 | //! 30 | //! $ # add this crate as a dependency 31 | //! $ edit Cargo.toml && cat $_ 32 | //! [dependencies] 33 | //! riscv-rt = "0.6.1" 34 | //! panic-halt = "0.2.0" 35 | //! 36 | //! $ # memory layout of the device 37 | //! $ edit memory.x && cat $_ 38 | //! MEMORY 39 | //! { 40 | //! RAM : ORIGIN = 0x80000000, LENGTH = 16K 41 | //! FLASH : ORIGIN = 0x20000000, LENGTH = 16M 42 | //! } 43 | //! 44 | //! REGION_ALIAS("REGION_TEXT", FLASH); 45 | //! REGION_ALIAS("REGION_RODATA", FLASH); 46 | //! REGION_ALIAS("REGION_DATA", RAM); 47 | //! REGION_ALIAS("REGION_BSS", RAM); 48 | //! REGION_ALIAS("REGION_HEAP", RAM); 49 | //! REGION_ALIAS("REGION_STACK", RAM); 50 | //! 51 | //! $ edit src/main.rs && cat $_ 52 | //! ``` 53 | //! 54 | //! ``` ignore,no_run 55 | //! #![no_std] 56 | //! #![no_main] 57 | //! 58 | //! extern crate panic_halt; 59 | //! 60 | //! use riscv_rt::entry; 61 | //! 62 | //! // use `main` as the entry point of this application 63 | //! // `main` is not allowed to return 64 | //! #[entry] 65 | //! fn main() -> ! { 66 | //! // do something here 67 | //! loop { } 68 | //! } 69 | //! ``` 70 | //! 71 | //! ``` text 72 | //! $ mkdir .cargo && edit .cargo/config && cat $_ 73 | //! [target.riscv32imac-unknown-none-elf] 74 | //! rustflags = [ 75 | //! "-C", "link-arg=-Tmemory.x", 76 | //! "-C", "link-arg=-Tlink.x", 77 | //! ] 78 | //! 79 | //! [build] 80 | //! target = "riscv32imac-unknown-none-elf" 81 | //! $ edit build.rs && cat $_ 82 | //! ``` 83 | //! 84 | //! ``` ignore,no_run 85 | //! use std::env; 86 | //! use std::fs; 87 | //! use std::path::PathBuf; 88 | //! 89 | //! fn main() { 90 | //! let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 91 | //! 92 | //! // Put the linker script somewhere the linker can find it. 93 | //! fs::write(out_dir.join("memory.x"), include_bytes!("memory.x")).unwrap(); 94 | //! println!("cargo:rustc-link-search={}", out_dir.display()); 95 | //! println!("cargo:rerun-if-changed=memory.x"); 96 | //! 97 | //! println!("cargo:rerun-if-changed=build.rs"); 98 | //! } 99 | //! ``` 100 | //! 101 | //! ``` text 102 | //! $ cargo build 103 | //! 104 | //! $ riscv32-unknown-elf-objdump -Cd $(find target -name app) | head 105 | //! 106 | //! Disassembly of section .text: 107 | //! 108 | //! 20000000 <_start>: 109 | //! 20000000: 800011b7 lui gp,0x80001 110 | //! 20000004: 80018193 addi gp,gp,-2048 # 80000800 <_stack_start+0xffffc800> 111 | //! 20000008: 80004137 lui sp,0x80004 112 | //! ``` 113 | //! 114 | //! # Symbol interfaces 115 | //! 116 | //! This crate makes heavy use of symbols, linker sections and linker scripts to 117 | //! provide most of its functionality. Below are described the main symbol 118 | //! interfaces. 119 | //! 120 | //! ## `memory.x` 121 | //! 122 | //! This file supplies the information about the device to the linker. 123 | //! 124 | //! ### `MEMORY` 125 | //! 126 | //! The main information that this file must provide is the memory layout of 127 | //! the device in the form of the `MEMORY` command. The command is documented 128 | //! [here][2], but at a minimum you'll want to create at least one memory region. 129 | //! 130 | //! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html 131 | //! 132 | //! To support different relocation models (RAM-only, FLASH+RAM) multiple regions are used: 133 | //! 134 | //! - `REGION_TEXT` - for `.init`, `.trap` and `.text` sections 135 | //! - `REGION_RODATA` - for `.rodata` section and storing initial values for `.data` section 136 | //! - `REGION_DATA` - for `.data` section 137 | //! - `REGION_BSS` - for `.bss` section 138 | //! - `REGION_HEAP` - for the heap area 139 | //! - `REGION_STACK` - for hart stacks 140 | //! 141 | //! Specific aliases for these regions must be defined in `memory.x` file (see example below). 142 | //! 143 | //! ### `_stext` 144 | //! 145 | //! This symbol provides the loading address of `.text` section. This value can be changed 146 | //! to override the loading address of the firmware (for example, in case of bootloader present). 147 | //! 148 | //! If omitted this symbol value will default to `ORIGIN(REGION_TEXT)`. 149 | //! 150 | //! ### `_stack_start` 151 | //! 152 | //! This symbol provides the address at which the call stack will be allocated. 153 | //! The call stack grows downwards so this address is usually set to the highest 154 | //! valid RAM address plus one (this *is* an invalid address but the processor 155 | //! will decrement the stack pointer *before* using its value as an address). 156 | //! 157 | //! In case of multiple harts present, this address defines the initial stack pointer for hart 0. 158 | //! Stack pointer for hart `N` is calculated as `_stack_start - N * _hart_stack_size`. 159 | //! 160 | //! If omitted this symbol value will default to `ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)`. 161 | //! 162 | //! #### Example 163 | //! 164 | //! Allocating the call stack on a different RAM region. 165 | //! 166 | //! ``` text 167 | //! MEMORY 168 | //! { 169 | //! L2_LIM : ORIGIN = 0x08000000, LENGTH = 1M 170 | //! RAM : ORIGIN = 0x80000000, LENGTH = 16K 171 | //! FLASH : ORIGIN = 0x20000000, LENGTH = 16M 172 | //! } 173 | //! 174 | //! REGION_ALIAS("REGION_TEXT", FLASH); 175 | //! REGION_ALIAS("REGION_RODATA", FLASH); 176 | //! REGION_ALIAS("REGION_DATA", RAM); 177 | //! REGION_ALIAS("REGION_BSS", RAM); 178 | //! REGION_ALIAS("REGION_HEAP", RAM); 179 | //! REGION_ALIAS("REGION_STACK", L2_LIM); 180 | //! 181 | //! _stack_start = ORIGIN(L2_LIM) + LENGTH(L2_LIM); 182 | //! ``` 183 | //! 184 | //! ### `_max_hart_id` 185 | //! 186 | //! This symbol defines the maximum hart id supported. All harts with id 187 | //! greater than `_max_hart_id` will be redirected to `abort()`. 188 | //! 189 | //! This symbol is supposed to be redefined in platform support crates for 190 | //! multi-core targets. 191 | //! 192 | //! If omitted this symbol value will default to 0 (single core). 193 | //! 194 | //! ### `_hart_stack_size` 195 | //! 196 | //! This symbol defines stack area size for *one* hart. 197 | //! 198 | //! If omitted this symbol value will default to 2K. 199 | //! 200 | //! ### `_heap_size` 201 | //! 202 | //! This symbol provides the size of a heap region. The default value is 0. You can set `_heap_size` 203 | //! to a non-zero value if you are planning to use heap allocations. 204 | //! 205 | //! ### `_sheap` 206 | //! 207 | //! This symbol is located in RAM right after the `.bss` and `.data` sections. 208 | //! You can use the address of this symbol as the start address of a heap 209 | //! region. This symbol is 4 byte aligned so that address will be a multiple of 4. 210 | //! 211 | //! #### Example 212 | //! 213 | //! ``` no_run 214 | //! extern crate some_allocator; 215 | //! 216 | //! extern "C" { 217 | //! static _sheap: u8; 218 | //! static _heap_size: u8; 219 | //! } 220 | //! 221 | //! fn main() { 222 | //! unsafe { 223 | //! let heap_bottom = &_sheap as *const u8 as usize; 224 | //! let heap_size = &_heap_size as *const u8 as usize; 225 | //! some_allocator::initialize(heap_bottom, heap_size); 226 | //! } 227 | //! } 228 | //! ``` 229 | //! 230 | //! ### `_mp_hook` 231 | //! 232 | //! This function is called from all the harts and must return true only for one hart, 233 | //! which will perform memory initialization. For other harts it must return false 234 | //! and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). 235 | //! The parameter `hartid` specifies the hartid of the caller. 236 | //! 237 | //! This function can be redefined in the following way: 238 | //! 239 | //! ``` no_run 240 | //! #[export_name = "_mp_hook"] 241 | //! pub extern "Rust" fn mp_hook(hartid: usize) -> bool { 242 | //! // ... 243 | //! } 244 | //! ``` 245 | //! 246 | //! Default implementation of this function wakes hart 0 and busy-loops all the other harts. 247 | //! 248 | //! ### `ExceptionHandler` 249 | //! 250 | //! This function is called when exception is occured. The exception reason can be decoded from the 251 | //! `mcause`/`scause` register. 252 | //! 253 | //! This function can be redefined in the following way: 254 | //! 255 | //! ``` no_run 256 | //! #[export_name = "ExceptionHandler"] 257 | //! fn custom_exception_handler(trap_frame: &riscv_rt::TrapFrame) -> ! { 258 | //! // ... 259 | //! } 260 | //! ``` 261 | //! or 262 | //! ``` no_run 263 | //! #[no_mangle] 264 | //! fn ExceptionHandler(trap_frame: &riscv_rt::TrapFrame) -> ! { 265 | //! // ... 266 | //! } 267 | //! ``` 268 | //! 269 | //! Default implementation of this function stucks in a busy-loop. 270 | //! 271 | //! 272 | //! ### Core interrupt handlers 273 | //! 274 | //! This functions are called when corresponding interrupt is occured. 275 | //! You can define an interrupt handler with one of the following names: 276 | //! * `UserSoft` 277 | //! * `SupervisorSoft` 278 | //! * `MachineSoft` 279 | //! * `UserTimer` 280 | //! * `SupervisorTimer` 281 | //! * `MachineTimer` 282 | //! * `UserExternal` 283 | //! * `SupervisorExternal` 284 | //! * `MachineExternal` 285 | //! 286 | //! For example: 287 | //! ``` no_run 288 | //! #[export_name = "MachineTimer"] 289 | //! fn custom_timer_handler() { 290 | //! // ... 291 | //! } 292 | //! ``` 293 | //! or 294 | //! ``` no_run 295 | //! #[no_mangle] 296 | //! fn MachineTimer() { 297 | //! // ... 298 | //! } 299 | //! ``` 300 | //! 301 | //! If interrupt handler is not explicitly defined, `DefaultHandler` is called. 302 | //! 303 | //! ### `DefaultHandler` 304 | //! 305 | //! This function is called when interrupt without defined interrupt handler is occured. 306 | //! The interrupt reason can be decoded from the `mcause`/`scause` register. 307 | //! 308 | //! This function can be redefined in the following way: 309 | //! 310 | //! ``` no_run 311 | //! #[export_name = "DefaultHandler"] 312 | //! fn custom_interrupt_handler() { 313 | //! // ... 314 | //! } 315 | //! ``` 316 | //! or 317 | //! ``` no_run 318 | //! #[no_mangle] 319 | //! fn DefaultHandler() { 320 | //! // ... 321 | //! } 322 | //! ``` 323 | //! 324 | //! Default implementation of this function stucks in a busy-loop. 325 | //! 326 | //! # Features 327 | //! 328 | //! ## `single-hart` 329 | //! 330 | //! This feature saves a little code size if there is only one hart on the target. 331 | //! 332 | //! ## `s-mode` 333 | //! 334 | //! The supervisor mode feature (`s-mode`) can be activated via [Cargo features](https://doc.rust-lang.org/cargo/reference/features.html). 335 | //! 336 | //! For example: 337 | //! ``` text 338 | //! [dependencies] 339 | //! riscv-rt = {features=["s-mode"]} 340 | //! ``` 341 | //! Internally, riscv-rt uses different versions of precompiled static libraries 342 | //! for (i) machine mode and (ii) supervisor mode. If the `s-mode` feature was activated, 343 | //! the build script selects the s-mode library. While most registers/instructions have variants for 344 | //! both `mcause` and `scause`, the `mhartid` hardware thread register is not available in supervisor 345 | //! mode. Instead, the hartid is passed as parameter by a bootstrapping firmware (i.e., SBI). 346 | //! 347 | //! Use case: QEMU supports [OpenSBI](https://github.com/riscv-software-src/opensbi) as default firmware. 348 | //! Using the SBI requires riscv-rt to be run in supervisor mode instead of machine mode. 349 | //! ``` text 350 | //! APP_BINARY=$(find target -name app) 351 | //! sudo qemu-system-riscv64 -m 2G -nographic -machine virt -kernel $APP_BINARY 352 | //! ``` 353 | //! It requires the memory layout to be non-overlapping, like 354 | //! ``` text 355 | //! MEMORY 356 | //! { 357 | //! RAM : ORIGIN = 0x80200000, LENGTH = 0x8000000 358 | //! FLASH : ORIGIN = 0x20000000, LENGTH = 16M 359 | //! } 360 | //! ``` 361 | 362 | // NOTE: Adapted from cortex-m/src/lib.rs 363 | #![no_std] 364 | #![deny(missing_docs)] 365 | 366 | #[cfg(riscv)] 367 | mod asm; 368 | 369 | use core::sync::atomic::{compiler_fence, Ordering}; 370 | 371 | #[cfg(feature = "s-mode")] 372 | use riscv::register::{scause as xcause, stvec as xtvec, stvec::TrapMode as xTrapMode}; 373 | 374 | #[cfg(not(feature = "s-mode"))] 375 | use riscv::register::{mcause as xcause, mtvec as xtvec, mtvec::TrapMode as xTrapMode}; 376 | 377 | #[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))] 378 | use riscv::register::mhartid; 379 | 380 | pub use riscv_rt_macros::{entry, pre_init}; 381 | 382 | #[export_name = "error: riscv-rt appears more than once in the dependency graph"] 383 | #[doc(hidden)] 384 | pub static __ONCE__: () = (); 385 | 386 | /// Rust entry point (_start_rust) 387 | /// 388 | /// Zeros bss section, initializes data section and calls main. This function never returns. 389 | /// 390 | /// # Safety 391 | /// 392 | /// This function must be called only from assembly `_start` function. 393 | /// Do **NOT** call this function directly. 394 | #[link_section = ".init.rust"] 395 | #[export_name = "_start_rust"] 396 | pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! { 397 | #[rustfmt::skip] 398 | extern "Rust" { 399 | // This symbol will be provided by the user via `#[entry]` 400 | fn main(a0: usize, a1: usize, a2: usize) -> !; 401 | 402 | // This symbol will be provided by the user via `#[pre_init]` 403 | fn __pre_init(); 404 | 405 | fn _setup_interrupts(); 406 | 407 | fn _mp_hook(hartid: usize) -> bool; 408 | } 409 | 410 | #[cfg(not(feature = "single-hart"))] 411 | let run_init = { 412 | // sbi passes hartid as first parameter (a0) 413 | #[cfg(feature = "s-mode")] 414 | let hartid = a0; 415 | #[cfg(not(feature = "s-mode"))] 416 | let hartid = mhartid::read(); 417 | 418 | _mp_hook(hartid) 419 | }; 420 | #[cfg(feature = "single-hart")] 421 | let run_init = true; 422 | 423 | if run_init { 424 | __pre_init(); 425 | 426 | // Initialize RAM 427 | // 1. Copy over .data from flash to RAM 428 | // 2. Zero out .bss 429 | 430 | #[cfg(target_arch = "riscv32")] 431 | core::arch::asm!( 432 | " 433 | // Copy over .data 434 | la {start},_sdata 435 | la {end},_edata 436 | la {input},_sidata 437 | 438 | bgeu {start},{end},2f 439 | 1: 440 | lw {a},0({input}) 441 | addi {input},{input},4 442 | sw {a},0({start}) 443 | addi {start},{start},4 444 | bltu {start},{end},1b 445 | 446 | 2: 447 | li {a},0 448 | li {input},0 449 | 450 | // Zero out .bss 451 | la {start},_sbss 452 | la {end},_ebss 453 | 454 | bgeu {start},{end},3f 455 | 2: 456 | sw zero,0({start}) 457 | addi {start},{start},4 458 | bltu {start},{end},2b 459 | 460 | 3: 461 | li {start},0 462 | li {end},0 463 | ", 464 | start = out(reg) _, 465 | end = out(reg) _, 466 | input = out(reg) _, 467 | a = out(reg) _, 468 | ); 469 | 470 | #[cfg(target_arch = "riscv64")] 471 | core::arch::asm!( 472 | " 473 | // Copy over .data 474 | la {start},_sdata 475 | la {end},_edata 476 | la {input},_sidata 477 | 478 | bgeu {start},{end},2f 479 | 480 | 1: // .data Main Loop 481 | ld {a},0({input}) 482 | addi {input},{input},8 483 | sd {a},0({start}) 484 | addi {start},{start},8 485 | bltu {start},{end},1b 486 | 487 | 2: // .data zero registers 488 | li {a},0 489 | li {input},0 490 | 491 | la {start},_sbss 492 | la {end},_ebss 493 | 494 | bgeu {start},{end},4f 495 | 496 | 3: // .bss main loop 497 | sd zero,0({start}) 498 | addi {start},{start},8 499 | bltu {start},{end},3b 500 | 501 | 4: // .bss zero registers 502 | // Zero out used registers 503 | li {start},0 504 | li {end},0 505 | ", 506 | start = out(reg) _, 507 | end = out(reg) _, 508 | input = out(reg) _, 509 | a = out(reg) _, 510 | ); 511 | 512 | compiler_fence(Ordering::SeqCst); 513 | } 514 | 515 | // TODO: Enable FPU when available 516 | 517 | _setup_interrupts(); 518 | 519 | main(a0, a1, a2); 520 | } 521 | 522 | /// Registers saved in trap handler 523 | #[allow(missing_docs)] 524 | #[repr(C)] 525 | #[derive(Debug)] 526 | pub struct TrapFrame { 527 | pub ra: usize, 528 | pub t0: usize, 529 | pub t1: usize, 530 | pub t2: usize, 531 | pub t3: usize, 532 | pub t4: usize, 533 | pub t5: usize, 534 | pub t6: usize, 535 | pub a0: usize, 536 | pub a1: usize, 537 | pub a2: usize, 538 | pub a3: usize, 539 | pub a4: usize, 540 | pub a5: usize, 541 | pub a6: usize, 542 | pub a7: usize, 543 | } 544 | 545 | /// Trap entry point rust (_start_trap_rust) 546 | /// 547 | /// `scause`/`mcause` is read to determine the cause of the trap. XLEN-1 bit indicates 548 | /// if it's an interrupt or an exception. The result is examined and ExceptionHandler 549 | /// or one of the core interrupt handlers is called. 550 | /// 551 | /// # Safety 552 | /// 553 | /// This function must be called only from assembly `_start_trap` function. 554 | /// Do **NOT** call this function directly. 555 | #[link_section = ".trap.rust"] 556 | #[export_name = "_start_trap_rust"] 557 | pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { 558 | extern "C" { 559 | fn ExceptionHandler(trap_frame: &TrapFrame); 560 | fn DefaultHandler(); 561 | } 562 | 563 | let cause = xcause::read(); 564 | 565 | if cause.is_exception() { 566 | ExceptionHandler(&*trap_frame) 567 | } else if cause.code() < __INTERRUPTS.len() { 568 | let h = &__INTERRUPTS[cause.code()]; 569 | if h.reserved == 0 { 570 | DefaultHandler(); 571 | } else { 572 | (h.handler)(); 573 | } 574 | } else { 575 | DefaultHandler(); 576 | } 577 | } 578 | 579 | #[doc(hidden)] 580 | #[no_mangle] 581 | #[allow(unused_variables, non_snake_case)] 582 | pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! { 583 | loop { 584 | // Prevent this from turning into a UDF instruction 585 | // see rust-lang/rust#28728 for details 586 | continue; 587 | } 588 | } 589 | 590 | #[doc(hidden)] 591 | #[no_mangle] 592 | #[allow(unused_variables, non_snake_case)] 593 | pub fn DefaultInterruptHandler() { 594 | loop { 595 | // Prevent this from turning into a UDF instruction 596 | // see rust-lang/rust#28728 for details 597 | continue; 598 | } 599 | } 600 | 601 | /* Interrupts */ 602 | #[doc(hidden)] 603 | pub enum Interrupt { 604 | UserSoft, 605 | SupervisorSoft, 606 | MachineSoft, 607 | UserTimer, 608 | SupervisorTimer, 609 | MachineTimer, 610 | UserExternal, 611 | SupervisorExternal, 612 | MachineExternal, 613 | } 614 | 615 | pub use self::Interrupt as interrupt; 616 | 617 | extern "C" { 618 | fn UserSoft(); 619 | fn SupervisorSoft(); 620 | fn MachineSoft(); 621 | fn UserTimer(); 622 | fn SupervisorTimer(); 623 | fn MachineTimer(); 624 | fn UserExternal(); 625 | fn SupervisorExternal(); 626 | fn MachineExternal(); 627 | } 628 | 629 | #[doc(hidden)] 630 | pub union Vector { 631 | pub handler: unsafe extern "C" fn(), 632 | pub reserved: usize, 633 | } 634 | 635 | #[doc(hidden)] 636 | #[no_mangle] 637 | pub static __INTERRUPTS: [Vector; 12] = [ 638 | Vector { handler: UserSoft }, 639 | Vector { 640 | handler: SupervisorSoft, 641 | }, 642 | Vector { reserved: 0 }, 643 | Vector { 644 | handler: MachineSoft, 645 | }, 646 | Vector { handler: UserTimer }, 647 | Vector { 648 | handler: SupervisorTimer, 649 | }, 650 | Vector { reserved: 0 }, 651 | Vector { 652 | handler: MachineTimer, 653 | }, 654 | Vector { 655 | handler: UserExternal, 656 | }, 657 | Vector { 658 | handler: SupervisorExternal, 659 | }, 660 | Vector { reserved: 0 }, 661 | Vector { 662 | handler: MachineExternal, 663 | }, 664 | ]; 665 | 666 | #[doc(hidden)] 667 | #[no_mangle] 668 | #[rustfmt::skip] 669 | pub unsafe extern "Rust" fn default_pre_init() {} 670 | 671 | #[doc(hidden)] 672 | #[no_mangle] 673 | #[rustfmt::skip] 674 | #[cfg(not(feature = "single-hart"))] 675 | pub extern "Rust" fn default_mp_hook(hartid: usize) -> bool { 676 | match hartid { 677 | 0 => true, 678 | _ => loop { 679 | unsafe { riscv::asm::wfi() } 680 | }, 681 | } 682 | } 683 | 684 | /// Default implementation of `_setup_interrupts` that sets `mtvec`/`stvec` to a trap handler address. 685 | #[doc(hidden)] 686 | #[no_mangle] 687 | #[rustfmt::skip] 688 | pub unsafe extern "Rust" fn default_setup_interrupts() { 689 | extern "C" { 690 | fn _start_trap(); 691 | } 692 | 693 | xtvec::write(_start_trap as usize, xTrapMode::Direct); 694 | } 695 | --------------------------------------------------------------------------------