Selaa lähdekoodia

Merge remote-tracking branch 'riscv-rt/master' into add-riscv-rt

Román Cárdenas 1 vuosi sitten
vanhempi
commit
0bae21672b

+ 1 - 0
riscv-rt/.github/CODEOWNERS

@@ -0,0 +1 @@
+* @rust-embedded/riscv

+ 49 - 0
riscv-rt/.github/workflows/build.yaml

@@ -0,0 +1,49 @@
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+  merge_group:
+
+name: Build check
+
+jobs:
+  build-riscv:
+    strategy:
+      matrix:
+        # All generated code should be running on stable now, MRSV is 1.59.0
+        toolchain: [ stable, nightly, 1.59.0 ]
+        target:
+          - riscv32i-unknown-none-elf
+          - riscv32imc-unknown-none-elf
+          - riscv32imac-unknown-none-elf
+          - riscv64imac-unknown-none-elf
+          - riscv64gc-unknown-none-elf
+        example:
+          - empty
+          - multi_core
+        cargo_flags: [ "--no-default-features", "--all-features" ]
+        include:
+          # Nightly is only for reference and allowed to fail
+          - toolchain: nightly
+            experimental: true
+    runs-on: ubuntu-latest
+    continue-on-error: ${{ matrix.experimental || false }}
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{ matrix.toolchain }}
+          targets: ${{ matrix.target }}
+      - name: Build library
+        run: cargo build --target ${{ matrix.target }} ${{ matrix.cargo_flags }}
+      - name: Build example
+        run: RUSTFLAGS="-C link-arg=-Texamples/device.x" cargo build --target ${{ matrix.target }} --example ${{ matrix.example }} ${{ matrix.cargo_flags }}
+  
+  # Job to check that all the builds succeeded
+  build-check:
+    needs:
+    - build-riscv
+    runs-on: ubuntu-latest
+    if: always()
+    steps:
+      - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

+ 20 - 0
riscv-rt/.github/workflows/changelog.yaml

@@ -0,0 +1,20 @@
+name: Check CHANGELOG.md
+
+on:
+  merge_group:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
+
+jobs:
+  changelog-check:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Check for CHANGELOG.md
+        uses: dangoslen/changelog-enforcer@v3
+        with:
+          skipLabels: 'skip changelog'
+          missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.'
+

+ 42 - 0
riscv-rt/.github/workflows/clippy.yaml

@@ -0,0 +1,42 @@
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+  merge_group:
+
+name: Lints compliance check
+
+env:
+  CLIPPY_PARAMS: -W clippy::all -W clippy::pedantic -W clippy::nursery -W clippy::cargo
+
+jobs:
+  clippy:
+    strategy:
+      matrix:
+        toolchain: [ stable, nightly ]
+        cargo_flags:
+          - "--no-default-features"
+          - "--all-features"
+        include:
+          # Nightly is only for reference and allowed to fail
+          - toolchain: nightly
+            experimental: true
+    runs-on: ubuntu-latest
+    continue-on-error: ${{ matrix.experimental || false }}
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{ matrix.toolchain }}
+          components: clippy
+      - name: Run clippy
+        run: cargo clippy --all ${{ matrix.cargo_flags }} -- -D warnings
+
+   # Job to check that all the lint checks succeeded
+  clippy-check:
+    needs:
+    - clippy
+    runs-on: ubuntu-latest
+    if: always()
+    steps:
+      - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

+ 17 - 0
riscv-rt/.github/workflows/label.yaml

@@ -0,0 +1,17 @@
+name: Check Labels
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
+
+jobs:
+  label-check:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: mheap/github-action-required-labels@v5
+        with:
+          mode: exactly
+          count: 0
+          labels: "work in progress, do not merge"
+          add_comment: true
+          message: "This PR is being prevented from merging because it presents one of the blocking labels: {{ provided }}."

+ 19 - 0
riscv-rt/.github/workflows/rustfmt.yaml

@@ -0,0 +1,19 @@
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+  merge_group:
+
+name: Code formatting check
+
+jobs:
+  rustfmt:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dtolnay/rust-toolchain@stable
+        with:
+          components: rustfmt
+      - name: Run Rustfmt
+        run: cargo fmt --all -- --check --verbose

+ 7 - 0
riscv-rt/.gitignore

@@ -0,0 +1,7 @@
+Cargo.lock
+target/
+bin/*.after
+bin/*.before
+bin/*.o
+
+.vscode/

+ 128 - 0
riscv-rt/CHANGELOG.md

@@ -0,0 +1,128 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased]
+
+### Added
+
+- New GitHub workflow for checking invalid labels in PRs
+- New GitHub workflow for checking modifications on CHANGELOG.md
+- New GitHub workflow for checking clippy lints in PRs
+- Optional cargo feature `single-hart` for single CPU targets
+
+### Changed
+
+- Use inline assembly instead of pre-compiled blobs
+- Removed bors in favor of GitHub Merge Queue
+- `start_trap_rust` is now marked as `unsafe`
+- Implement `r0` as inline assembly
+- Use `${ARCH_WIDTH}` in `link.x.in` to adapt to different archs
+- mhartid CSR is no longer read in single-hart mode, assumed zero
+- Ensure stack pointer is 16-byte aligned before jumping to Rust entry point
+
+## [v0.11.0] - 2023-01-18
+
+### Changed
+
+- Update `riscv` to version 0.10.1 fixing a critical section bug
+
+## [v0.10.0] - 2022-11-04
+
+### Added
+
+- Optional cargo feature `s-mode` for supervisor mode, including conditional compilation for supervisor/machine mode instructions.
+
+### Changed
+
+- Remove superfluous parentheses from link.x, which caused linker errors with nightly.
+- Changed `mp_hook` signature, hartid as passed as usize parameter by the caller (required for `s-mode` feature).
+- Update `riscv` to version 0.9
+
+## [v0.9.0] - 2022-07-01
+
+### Added
+
+- Pass `a0..a2` register values to the `#[entry]` function.
+
+### Changed
+
+- Update `riscv` to version 0.8
+- Update `riscv-rt-macros` to 0.2.0
+- Update Minimum Supported Rust Version to 1.59
+- The main symbol is no longer randomly generated in the `#[entry]` macro, instead it uses `__risc_v_rt__main`.
+
+### Removed
+
+- Remove `inline-asm` feature which is now always enabled
+
+## [v0.8.1] - 2022-01-25
+
+### Added
+
+- Enable float support for targets with extension sets F and D
+- Add ability to override trap handling mechanism
+
+### Changed
+
+- Update `riscv` to version 0.7
+- Update `quote` to version 1.0
+- Update `proc-macro2` to version 1.0
+- Update `rand` to version to version 0.7.3
+
+## [v0.8.0] - 2020-07-18
+
+### Changed
+
+- Update `riscv` to version 0.6
+- Update Minimum Supported Rust Version to 1.42.0
+
+## [v0.7.2] - 2020-07-16
+
+### Changed
+
+- Preserve `.eh_frame` and `.eh_frame_hdr` sections
+- Place `.srodata` and `.srodata.*` sections in `.rodata`
+
+## [v0.7.1] - 2020-06-02
+
+### Added
+
+- Add support to initialize custom interrupt controllers.
+
+### Changed
+
+- Exception handler may return now
+
+## [v0.7.0] - 2020-03-10
+
+### Added
+
+- Assure address of PC at startup
+- Implement interrupt and exception handling
+- Add support for the `riscv32i-unknown-none-elf` target
+- Added Changelog
+
+### Fixed
+
+- Fix linker script compatibility with GNU linker
+
+### Changed
+
+- Move `abort` out of the `.init` section
+- Update `r0` to v1.0.0
+- Set MSRV to 1.38
+
+
+[Unreleased]: https://github.com/rust-embedded/riscv-rt/compare/v0.11.0..HEAD
+[v0.10.1]: https://github.com/rust-embedded/riscv-rt/compare/v0.10.0...v0.11.0
+[v0.10.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.9.1...v0.10.0
+[v0.9.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.8.1...v0.9.0
+[v0.8.1]: https://github.com/rust-embedded/riscv-rt/compare/v0.8.0...v0.8.1
+[v0.8.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.7.2...v0.8.0
+[v0.7.2]: https://github.com/rust-embedded/riscv-rt/compare/v0.7.1...v0.7.2
+[v0.7.1]: https://github.com/rust-embedded/riscv-rt/compare/v0.7.0...v0.7.1
+[v0.7.0]: https://github.com/rust-embedded/riscv-rt/compare/v0.6.1...v0.7.0

+ 37 - 0
riscv-rt/CODE_OF_CONDUCT.md

@@ -0,0 +1,37 @@
+# The Rust Code of Conduct
+
+## Conduct
+
+**Contact**: [RISC-V team](https://github.com/rust-embedded/wg#the-riscv-team)
+
+* 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.
+* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
+* Please be kind and courteous. There's no need to be mean or rude.
+* 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.
+* 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.
+* 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.
+* 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.
+* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
+
+## Moderation
+
+These are the policies for upholding our community's standards of conduct.
+
+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.)
+2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
+3. Moderators will first respond to such remarks with a warning.
+4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off.
+5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
+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.
+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.
+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.
+
+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.
+
+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.
+
+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).
+
+*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/).*
+
+[team]: https://github.com/rust-embedded/wg#the-riscv-team

+ 25 - 0
riscv-rt/Cargo.toml

@@ -0,0 +1,25 @@
+[package]
+name = "riscv-rt"
+version = "0.11.0"
+rust-version = "1.59"
+repository = "https://github.com/rust-embedded/riscv-rt"
+authors = ["The RISC-V Team <risc-v@teams.rust-embedded.org>"]
+categories = ["embedded", "no-std"]
+description = "Minimal runtime / startup for RISC-V CPU's"
+keywords = ["riscv", "runtime", "startup"]
+license = "ISC"
+edition = "2018"
+
+[features]
+s-mode = []
+single-hart = []
+
+[dependencies]
+riscv = "0.10"
+riscv-rt-macros = { path = "macros", version = "0.2.0" }
+
+[dev-dependencies]
+panic-halt = "0.2.0"
+
+[build-dependencies]
+riscv-target = "0.1.2"

+ 41 - 0
riscv-rt/README.md

@@ -0,0 +1,41 @@
+[![crates.io](https://img.shields.io/crates/d/riscv-rt.svg)](https://crates.io/crates/riscv-rt)
+[![crates.io](https://img.shields.io/crates/v/riscv-rt.svg)](https://crates.io/crates/riscv-rt)
+[![Build Status](https://travis-ci.org/rust-embedded/riscv-rt.svg?branch=master)](https://travis-ci.org/rust-embedded/riscv-rt)
+
+# `riscv-rt`
+
+> Minimal runtime / startup for RISC-V CPU's.
+
+This project is developed and maintained by the [RISC-V team][team].
+
+## [Documentation](https://docs.rs/crate/riscv-rt)
+
+## Minimum Supported Rust Version (MSRV)
+
+This crate is guaranteed to compile on stable Rust 1.59 and up. It *might*
+compile with older versions but that may change in any new patch release.
+
+## License
+
+Copyright 2018-2022 [RISC-V team][team]
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+## Code of Conduct
+
+Contribution to this crate is organized under the terms of the [Rust Code of
+Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises
+to intervene to uphold that code of conduct.
+
+[CoC]: CODE_OF_CONDUCT.md
+[team]: https://github.com/rust-embedded/wg#the-risc-v-team

+ 49 - 0
riscv-rt/build.rs

@@ -0,0 +1,49 @@
+// NOTE: Adapted from cortex-m/build.rs
+
+use riscv_target::Target;
+use std::{env, fs, io, path::PathBuf};
+
+fn add_linker_script(arch_width: u32) -> io::Result<()> {
+    // Read the file to a string and replace all occurrences of ${ARCH_WIDTH} with the arch width
+    let mut content = fs::read_to_string("link.x.in")?;
+    content = content.replace("${ARCH_WIDTH}", &arch_width.to_string());
+
+    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+
+    // Put the linker script somewhere the linker can find it
+    fs::write(out_dir.join("link.x"), content)?;
+    println!("cargo:rustc-link-search={}", out_dir.display());
+    println!("cargo:rerun-if-changed=link.x");
+
+    Ok(())
+}
+
+fn main() {
+    let target = env::var("TARGET").unwrap();
+    let _name = env::var("CARGO_PKG_NAME").unwrap();
+
+    // set configuration flags depending on the target
+    if target.starts_with("riscv") {
+        println!("cargo:rustc-cfg=riscv");
+        let target = Target::from_target_str(&target);
+
+        // generate the linker script
+        let arch_width = match target.bits {
+            32 => {
+                println!("cargo:rustc-cfg=riscv32");
+                4
+            }
+            64 => {
+                println!("cargo:rustc-cfg=riscv64");
+                8
+            }
+            _ => panic!("Unsupported bit width"),
+        };
+        add_linker_script(arch_width).unwrap();
+
+        // expose the ISA extensions
+        if target.has_extension('m') {
+            println!("cargo:rustc-cfg=riscvm");
+        }
+    }
+}

+ 14 - 0
riscv-rt/examples/device.x

@@ -0,0 +1,14 @@
+MEMORY
+{
+    RAM : ORIGIN = 0x80000000, LENGTH = 16K
+    FLASH : ORIGIN = 0x20000000, LENGTH = 4M
+}
+
+REGION_ALIAS("REGION_TEXT", FLASH);
+REGION_ALIAS("REGION_RODATA", FLASH);
+REGION_ALIAS("REGION_DATA", RAM);
+REGION_ALIAS("REGION_BSS", RAM);
+REGION_ALIAS("REGION_HEAP", RAM);
+REGION_ALIAS("REGION_STACK", RAM);
+
+INCLUDE link.x

+ 13 - 0
riscv-rt/examples/empty.rs

@@ -0,0 +1,13 @@
+#![no_std]
+#![no_main]
+
+extern crate panic_halt;
+extern crate riscv_rt;
+
+use riscv_rt::entry;
+
+#[entry]
+fn main() -> ! {
+    // do something here
+    loop {}
+}

+ 54 - 0
riscv-rt/examples/multi_core.rs

@@ -0,0 +1,54 @@
+#![no_std]
+#![no_main]
+
+extern crate panic_halt;
+extern crate riscv;
+extern crate riscv_rt;
+
+use riscv::asm::wfi;
+use riscv::register::{mie, mip};
+use riscv_rt::entry;
+
+#[export_name = "_mp_hook"]
+#[rustfmt::skip]
+pub extern "Rust" fn user_mp_hook(hartid: usize) -> bool {
+    if hartid == 0 {
+        true
+    } else {
+        let addr = 0x02000000 + hartid * 4;
+        unsafe {
+            // Clear IPI
+            (addr as *mut u32).write_volatile(0);
+
+            // Start listening for software interrupts
+            mie::set_msoft();
+
+            loop {
+                wfi();
+                if mip::read().msoft() {
+                    break;
+                }
+            }
+
+            // Stop listening for software interrupts
+            mie::clear_msoft();
+
+            // Clear IPI
+            (addr as *mut u32).write_volatile(0);
+        }
+        false
+    }
+}
+
+#[entry]
+fn main(hartid: usize) -> ! {
+    if hartid == 0 {
+        // Waking hart 1...
+        let addr = 0x02000004;
+        unsafe {
+            (addr as *mut u32).write_volatile(1);
+        }
+    }
+
+    loop {}
+}

+ 198 - 0
riscv-rt/link.x.in

@@ -0,0 +1,198 @@
+/* # Developer notes
+
+- Symbols that start with a double underscore (__) are considered "private"
+
+- Symbols that start with a single underscore (_) are considered "semi-public"; they can be
+  overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" {
+  static mut _heap_size }`).
+
+- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a
+  symbol if not dropped if it appears in or near the front of the linker arguments and "it's not
+  needed" by any of the preceding objects (linker arguments)
+
+- `PROVIDE` is used to provide default values that can be overridden by a user linker script
+
+- In this linker script, you may find symbols that look like `${...}` (e.g., `${ARCH_WIDTH}`).
+  These are wildcards used by the `build.rs` script to adapt to different target particularities.
+  Check `build.rs` for more details about these symbols.
+
+- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and*
+  the LMA of .data are all `${ARCH_WIDTH}`-byte aligned. These alignments are assumed by the RAM
+  initialization routine. There's also a second benefit: `${ARCH_WIDTH}`-byte aligned boundaries
+  means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`.
+*/
+
+PROVIDE(_stext = ORIGIN(REGION_TEXT));
+PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK));
+PROVIDE(_max_hart_id = 0);
+PROVIDE(_hart_stack_size = 2K);
+PROVIDE(_heap_size = 0);
+
+PROVIDE(UserSoft = DefaultHandler);
+PROVIDE(SupervisorSoft = DefaultHandler);
+PROVIDE(MachineSoft = DefaultHandler);
+PROVIDE(UserTimer = DefaultHandler);
+PROVIDE(SupervisorTimer = DefaultHandler);
+PROVIDE(MachineTimer = DefaultHandler);
+PROVIDE(UserExternal = DefaultHandler);
+PROVIDE(SupervisorExternal = DefaultHandler);
+PROVIDE(MachineExternal = DefaultHandler);
+
+PROVIDE(DefaultHandler = DefaultInterruptHandler);
+PROVIDE(ExceptionHandler = DefaultExceptionHandler);
+
+/* # Pre-initialization function */
+/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function,
+   then the function this points to will be called before the RAM is initialized. */
+PROVIDE(__pre_init = default_pre_init);
+
+/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */
+PROVIDE(_setup_interrupts = default_setup_interrupts);
+
+/* # Multi-processing hook function
+   fn _mp_hook() -> bool;
+
+   This function is called from all the harts and must return true only for one hart,
+   which will perform memory initialization. For other harts it must return false
+   and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
+*/
+PROVIDE(_mp_hook = default_mp_hook);
+
+/* # Start trap function override
+  By default uses the riscv crates default trap handler
+  but by providing the `_start_trap` symbol external crates can override.
+*/
+PROVIDE(_start_trap = default_start_trap);
+
+SECTIONS
+{
+  .text.dummy (NOLOAD) :
+  {
+    /* This section is intended to make _stext address work */
+    . = ABSOLUTE(_stext);
+  } > REGION_TEXT
+
+  .text _stext :
+  {
+    /* Put reset handler first in .text section so it ends up as the entry */
+    /* point of the program. */
+    KEEP(*(.init));
+    KEEP(*(.init.rust));
+    . = ALIGN(4);
+    *(.trap);
+    *(.trap.rust);
+    *(.text.abort);
+    *(.text .text.*);
+  } > REGION_TEXT
+
+  .rodata : ALIGN(4)
+  {
+    *(.srodata .srodata.*);
+    *(.rodata .rodata.*);
+
+    /* 4-byte align the end (VMA) of this section.
+       This is required by LLD to ensure the LMA of the following .data
+       section will have the correct alignment. */
+    . = ALIGN(4);
+  } > REGION_RODATA
+
+  .data : ALIGN(${ARCH_WIDTH})
+  {
+    _sidata = LOADADDR(.data);
+    _sdata = .;
+    /* Must be called __global_pointer$ for linker relaxations to work. */
+    PROVIDE(__global_pointer$ = . + 0x800);
+    *(.sdata .sdata.* .sdata2 .sdata2.*);
+    *(.data .data.*);
+    . = ALIGN(${ARCH_WIDTH});
+    _edata = .;
+  } > REGION_DATA AT > REGION_RODATA
+
+  .bss (NOLOAD) : ALIGN(${ARCH_WIDTH})
+  {
+    _sbss = .;
+    *(.sbss .sbss.* .bss .bss.*);
+    . = ALIGN(${ARCH_WIDTH});
+    _ebss = .;
+  } > REGION_BSS
+
+  /* fictitious region that represents the memory available for the heap */
+  .heap (NOLOAD) :
+  {
+    _sheap = .;
+    . += _heap_size;
+    . = ALIGN(4);
+    _eheap = .;
+  } > REGION_HEAP
+
+  /* fictitious region that represents the memory available for the stack */
+  .stack (NOLOAD) :
+  {
+    _estack = .;
+    . = ABSOLUTE(_stack_start);
+    _sstack = .;
+  } > REGION_STACK
+
+  /* fake output .got section */
+  /* Dynamic relocations are unsupported. This section is only used to detect
+     relocatable code in the input files and raise an error if relocatable code
+     is found */
+  .got (INFO) :
+  {
+    KEEP(*(.got .got.*));
+  }
+
+  .eh_frame (INFO) : { KEEP(*(.eh_frame)) }
+  .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) }
+}
+
+/* Do not exceed this mark in the error messages above                                    | */
+ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_DATA) % ${ARCH_WIDTH} == 0, "
+ERROR(riscv-rt): the start of the REGION_DATA must be ${ARCH_WIDTH}-byte aligned");
+
+ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned");
+
+ASSERT(ORIGIN(REGION_STACK) % 4 == 0, "
+ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned");
+
+ASSERT(_stext % 4 == 0, "
+ERROR(riscv-rt): `_stext` must be 4-byte aligned");
+
+ASSERT(_sdata % ${ARCH_WIDTH} == 0 && _edata % ${ARCH_WIDTH} == 0, "
+BUG(riscv-rt): .data is not ${ARCH_WIDTH}-byte aligned");
+
+ASSERT(_sidata % ${ARCH_WIDTH} == 0, "
+BUG(riscv-rt): the LMA of .data is not ${ARCH_WIDTH}-byte aligned");
+
+ASSERT(_sbss % ${ARCH_WIDTH} == 0 && _ebss % ${ARCH_WIDTH} == 0, "
+BUG(riscv-rt): .bss is not ${ARCH_WIDTH}-byte aligned");
+
+ASSERT(_sheap % 4 == 0, "
+BUG(riscv-rt): start of .heap is not 4-byte aligned");
+
+ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), "
+ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region.
+Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'");
+
+ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, "
+ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts.
+Consider changing `_max_hart_id` or `_hart_stack_size`.");
+
+ASSERT(SIZEOF(.got) == 0, "
+.got section detected in the input files. Dynamic relocations are not
+supported. If you are linking to C code compiled using the `gcc` crate
+then modify your build script to compile the C code _without_ the
+-fPIC flag. See the documentation of the `gcc::Config.fpic` method for
+details.");
+
+/* Do not exceed this mark in the error messages above                                    | */

+ 24 - 0
riscv-rt/macros/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+authors = [
+    "The RISC-V Team <risc-v@teams.rust-embedded.org>",
+    "Jorge Aparicio <jorge@japaric.io>",
+]
+categories = ["embedded", "no-std"]
+description = "Attributes re-exported in `riscv-rt`"
+documentation = "https://docs.rs/riscv-rt"
+keywords = ["riscv", "runtime", "startup"]
+license = "MIT OR Apache-2.0"
+name = "riscv-rt-macros"
+repository = "https://github.com/rust-embedded/riscv-rt"
+version = "0.2.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1.0"
+proc-macro2 = "1.0"
+
+[dependencies.syn]
+version = "1.0"
+features = ["extra-traits", "full"]

+ 207 - 0
riscv-rt/macros/src/lib.rs

@@ -0,0 +1,207 @@
+#![deny(warnings)]
+
+extern crate proc_macro;
+#[macro_use]
+extern crate quote;
+extern crate core;
+extern crate proc_macro2;
+#[macro_use]
+extern crate syn;
+
+use proc_macro2::Span;
+use syn::{parse, spanned::Spanned, FnArg, ItemFn, PathArguments, ReturnType, Type, Visibility};
+
+use proc_macro::TokenStream;
+
+/// Attribute to declare the entry point of the program
+///
+/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you
+/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
+/// private modules between the item and the root of the crate); if the item is in the root of the
+/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases.
+///
+/// The specified function will be called by the reset handler *after* RAM has been initialized.
+/// If present, the FPU will also be enabled before the function is called.
+///
+/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function)
+///
+/// # Properties
+///
+/// The entry point will be called by the reset handler. The program can't reference to the entry
+/// point, much less invoke it.
+///
+/// # Examples
+///
+/// - Simple entry point
+///
+/// ``` no_run
+/// # #![no_main]
+/// # use riscv_rt_macros::entry;
+/// #[entry]
+/// fn main() -> ! {
+///     loop {
+///         /* .. */
+///     }
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
+    let f = parse_macro_input!(input as ItemFn);
+
+    // check the function arguments
+    if f.sig.inputs.len() > 3 {
+        return parse::Error::new(
+            f.sig.inputs.last().unwrap().span(),
+            "`#[entry]` function has too many arguments",
+        )
+        .to_compile_error()
+        .into();
+    }
+    for arg in &f.sig.inputs {
+        match arg {
+            FnArg::Receiver(_) => {
+                return parse::Error::new(arg.span(), "invalid argument")
+                    .to_compile_error()
+                    .into();
+            }
+            FnArg::Typed(t) => {
+                if !is_simple_type(&t.ty, "usize") {
+                    return parse::Error::new(t.ty.span(), "argument type must be usize")
+                        .to_compile_error()
+                        .into();
+                }
+            }
+        }
+    }
+
+    // check the function signature
+    let valid_signature = f.sig.constness.is_none()
+        && f.sig.asyncness.is_none()
+        && f.vis == Visibility::Inherited
+        && f.sig.abi.is_none()
+        && f.sig.generics.params.is_empty()
+        && f.sig.generics.where_clause.is_none()
+        && f.sig.variadic.is_none()
+        && match f.sig.output {
+            ReturnType::Default => false,
+            ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
+        };
+
+    if !valid_signature {
+        return parse::Error::new(
+            f.span(),
+            "`#[entry]` function must have signature `[unsafe] fn([arg0: usize, ...]) -> !`",
+        )
+        .to_compile_error()
+        .into();
+    }
+
+    if !args.is_empty() {
+        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+            .to_compile_error()
+            .into();
+    }
+
+    // XXX should we blacklist other attributes?
+    let attrs = f.attrs;
+    let unsafety = f.sig.unsafety;
+    let args = f.sig.inputs;
+    let stmts = f.block.stmts;
+
+    quote!(
+        #[allow(non_snake_case)]
+        #[export_name = "main"]
+        #(#attrs)*
+        pub #unsafety fn __risc_v_rt__main(#args) -> ! {
+            #(#stmts)*
+        }
+    )
+    .into()
+}
+
+#[allow(unused)]
+fn is_simple_type(ty: &Type, name: &str) -> bool {
+    if let Type::Path(p) = ty {
+        if p.qself.is_none() && p.path.leading_colon.is_none() && p.path.segments.len() == 1 {
+            let segment = p.path.segments.first().unwrap();
+            if segment.ident == name && segment.arguments == PathArguments::None {
+                return true;
+            }
+        }
+    }
+    false
+}
+
+/// Attribute to mark which function will be called at the beginning of the reset handler.
+///
+/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. Also, if you
+/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
+/// private modules between the item and the root of the crate); if the item is in the root of the
+/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer
+/// releases.
+///
+/// The function must have the signature of `unsafe fn()`.
+///
+/// The function passed will be called before static variables are initialized. Any access of static
+/// variables will result in undefined behavior.
+///
+/// # Examples
+///
+/// ```
+/// # use riscv_rt_macros::pre_init;
+/// #[pre_init]
+/// unsafe fn before_main() {
+///     // do something here
+/// }
+///
+/// # fn main() {}
+/// ```
+#[proc_macro_attribute]
+pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
+    let f = parse_macro_input!(input as ItemFn);
+
+    // check the function signature
+    let valid_signature = f.sig.constness.is_none()
+        && f.sig.asyncness.is_none()
+        && f.vis == Visibility::Inherited
+        && f.sig.unsafety.is_some()
+        && f.sig.abi.is_none()
+        && f.sig.inputs.is_empty()
+        && f.sig.generics.params.is_empty()
+        && f.sig.generics.where_clause.is_none()
+        && f.sig.variadic.is_none()
+        && match f.sig.output {
+            ReturnType::Default => true,
+            ReturnType::Type(_, ref ty) => match **ty {
+                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
+                _ => false,
+            },
+        };
+
+    if !valid_signature {
+        return parse::Error::new(
+            f.span(),
+            "`#[pre_init]` function must have signature `unsafe fn()`",
+        )
+        .to_compile_error()
+        .into();
+    }
+
+    if !args.is_empty() {
+        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+            .to_compile_error()
+            .into();
+    }
+
+    // XXX should we blacklist other attributes?
+    let attrs = f.attrs;
+    let ident = f.sig.ident;
+    let block = f.block;
+
+    quote!(
+        #[export_name = "__pre_init"]
+        #(#attrs)*
+        pub unsafe fn #ident() #block
+    )
+    .into()
+}

+ 189 - 0
riscv-rt/src/asm.rs

@@ -0,0 +1,189 @@
+use core::arch::global_asm;
+
+/// Parse cfg attributes inside a global_asm call.
+macro_rules! cfg_global_asm {
+    {@inner, [$($x:tt)*], } => {
+        global_asm!{$($x)*}
+    };
+    (@inner, [$($x:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => {
+        #[cfg($meta)]
+        cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*}
+        #[cfg(not($meta))]
+        cfg_global_asm!{@inner, [$($x)*], $($rest)*}
+    };
+    {@inner, [$($x:tt)*], $asm:literal, $($rest:tt)*} => {
+        cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*}
+    };
+    {$($asms:tt)*} => {
+        cfg_global_asm!{@inner, [], $($asms)*}
+    };
+}
+
+// Entry point of all programs (_start). It initializes DWARF call frame information,
+// the stack pointer, the frame pointer (needed for closures to work in start_rust)
+// and the global pointer. Then it calls _start_rust.
+cfg_global_asm!(
+    ".section .init, \"ax\"
+    .global _start
+
+_start:",
+    #[cfg(riscv32)]
+    "lui ra, %hi(_abs_start)
+     jr %lo(_abs_start)(ra)",
+    #[cfg(riscv64)]
+    ".option push
+    .option norelax // to prevent an unsupported R_RISCV_ALIGN relocation from being generated
+1:
+    auipc ra, %pcrel_hi(1f)
+    ld ra, %pcrel_lo(1b)(ra)
+    jr ra
+    .align  3
+1:
+    .dword _abs_start
+    .option pop",
+    "
+_abs_start:
+    .option norelax
+    .cfi_startproc
+    .cfi_undefined ra",
+    #[cfg(feature = "s-mode")]
+    "csrw sie, 0
+    csrw sip, 0",
+    #[cfg(not(feature = "s-mode"))]
+    "csrw mie, 0
+    csrw mip, 0",
+    "li  x1, 0
+    li  x2, 0
+    li  x3, 0
+    li  x4, 0
+    li  x5, 0
+    li  x6, 0
+    li  x7, 0
+    li  x8, 0
+    li  x9, 0
+    // a0..a2 (x10..x12) skipped
+    li  x13, 0
+    li  x14, 0
+    li  x15, 0
+    li  x16, 0
+    li  x17, 0
+    li  x18, 0
+    li  x19, 0
+    li  x20, 0
+    li  x21, 0
+    li  x22, 0
+    li  x23, 0
+    li  x24, 0
+    li  x25, 0
+    li  x26, 0
+    li  x27, 0
+    li  x28, 0
+    li  x29, 0
+    li  x30, 0
+    li  x31, 0
+
+    .option push
+    .option norelax
+    la gp, __global_pointer$
+    .option pop
+    // Allocate stacks",
+    #[cfg(all(not(feature = "single-hart"), feature = "s-mode"))]
+    "mv t2, a0 // the hartid is passed as parameter by SMODE",
+    #[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))]
+    "csrr t2, mhartid",
+    #[cfg(not(feature = "single-hart"))]
+    "lui t0, %hi(_max_hart_id)
+    add t0, t0, %lo(_max_hart_id)
+    bgtu t2, t0, abort
+    lui t0, %hi(_hart_stack_size)
+    add t0, t0, %lo(_hart_stack_size)",
+    #[cfg(all(not(feature = "single-hart"), riscvm))]
+    "mul t0, t2, t0",
+    #[cfg(all(not(feature = "single-hart"), not(riscvm)))]
+    "beqz t2, 2f  // Jump if single-hart
+    mv t1, t2
+    mv t3, t0
+1:
+    add t0, t0, t3
+    addi t1, t1, -1
+    bnez t1, 1b
+2:  ",
+    "la t1, _stack_start",
+    #[cfg(not(feature = "single-hart"))]
+    "sub t1, t1, t0",
+    "andi sp, t1, -16 // Force 16-byte alignment
+    // Set frame pointer
+    add s0, sp, zero
+
+    jal zero, _start_rust
+
+    .cfi_endproc",
+);
+
+/// Trap entry point (_start_trap). It saves caller saved registers, calls
+/// _start_trap_rust, restores caller saved registers and then returns.
+///
+/// # Usage
+///
+/// The macro takes 5 arguments:
+/// - `$STORE`: the instruction used to store a register in the stack (e.g. `sd` for riscv64)
+/// - `$LOAD`: the instruction used to load a register from the stack (e.g. `ld` for riscv64)
+/// - `$BYTES`: the number of bytes used to store a register (e.g. 8 for riscv64)
+/// - `$TRAP_SIZE`: the number of registers to store in the stack (e.g. 32 for all the user registers)
+/// - list of tuples of the form `($REG, $LOCATION)`, where:
+///     - `$REG`: the register to store/load
+///     - `$LOCATION`: the location in the stack where to store/load the register
+#[rustfmt::skip]
+macro_rules! trap_handler {
+    ($STORE:ident, $LOAD:ident, $BYTES:literal, $TRAP_SIZE:literal, [$(($REG:ident, $LOCATION:literal)),*]) => {
+        // ensure we do not break that sp is 16-byte aligned
+        const _: () = assert!(($TRAP_SIZE * $BYTES) % 16 == 0);
+        global_asm!(
+        "
+            .section .trap, \"ax\"
+            .global default_start_trap
+        default_start_trap:",
+            // save space for trap handler in stack
+            concat!("addi sp, sp, -", stringify!($TRAP_SIZE * $BYTES)),
+            // save registers in the desired order
+            $(concat!(stringify!($STORE), " ", stringify!($REG), ", ", stringify!($LOCATION * $BYTES), "(sp)"),)*
+            // call rust trap handler
+            "add a0, sp, zero
+            jal ra, _start_trap_rust",
+            // restore registers in the desired order
+            $(concat!(stringify!($LOAD), " ", stringify!($REG), ", ", stringify!($LOCATION * $BYTES), "(sp)"),)*
+            // free stack
+            concat!("addi sp, sp, ", stringify!($TRAP_SIZE * $BYTES)),
+        );
+        cfg_global_asm!(
+            // return from trap
+            #[cfg(feature = "s-mode")]
+            "sret",
+            #[cfg(not(feature = "s-mode"))]
+            "mret",
+        );
+    };
+}
+
+#[rustfmt::skip]
+#[cfg(riscv32)]
+trap_handler!(
+    sw, lw, 4, 16,
+    [(ra, 0), (t0, 1), (t1, 2), (t2, 3), (t3, 4), (t4, 5), (t5, 6), (t6, 7),
+     (a0, 8), (a1, 9), (a2, 10), (a3, 11), (a4, 12), (a5, 13), (a6, 14), (a7, 15)]
+);
+#[rustfmt::skip]
+#[cfg(riscv64)]
+trap_handler!(
+    sd, ld, 8, 16,
+    [(ra, 0), (t0, 1), (t1, 2), (t2, 3), (t3, 4), (t4, 5), (t5, 6), (t6, 7),
+     (a0, 8), (a1, 9), (a2, 10), (a3, 11), (a4, 12), (a5, 13), (a6, 14), (a7, 15)]
+);
+
+// Make sure there is an abort when linking
+global_asm!(
+    ".section .text.abort
+     .globl abort
+abort:
+    j abort"
+);

+ 694 - 0
riscv-rt/src/lib.rs

@@ -0,0 +1,694 @@
+//! Minimal startup / runtime for RISC-V CPU's
+//!
+//! # Minimum Supported Rust Version (MSRV)
+//!
+//! This crate is guaranteed to compile on stable Rust 1.59 and up. It *might*
+//! compile with older versions but that may change in any new patch release.
+//!
+//! # Features
+//!
+//! This crate provides
+//!
+//! - Before main initialization of the `.bss` and `.data` sections.
+//!
+//! - `#[entry]` to declare the entry point of the program
+//! - `#[pre_init]` to run code *before* `static` variables are initialized
+//!
+//! - A linker script that encodes the memory layout of a generic RISC-V
+//!   microcontroller. This linker script is missing some information that must
+//!   be supplied through a `memory.x` file (see example below). This file
+//!   must be supplied using rustflags and listed *before* `link.x`. Arbitrary
+//!   filename can be use instead of `memory.x`.
+//!
+//! - A `_sheap` symbol at whose address you can locate a heap.
+//!
+//! - 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)
+//!
+//! ``` text
+//! $ cargo new --bin app && cd $_
+//!
+//! $ # add this crate as a dependency
+//! $ edit Cargo.toml && cat $_
+//! [dependencies]
+//! riscv-rt = "0.6.1"
+//! panic-halt = "0.2.0"
+//!
+//! $ # memory layout of the device
+//! $ edit memory.x && cat $_
+//! MEMORY
+//! {
+//!   RAM : ORIGIN = 0x80000000, LENGTH = 16K
+//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
+//! }
+//!
+//! REGION_ALIAS("REGION_TEXT", FLASH);
+//! REGION_ALIAS("REGION_RODATA", FLASH);
+//! REGION_ALIAS("REGION_DATA", RAM);
+//! REGION_ALIAS("REGION_BSS", RAM);
+//! REGION_ALIAS("REGION_HEAP", RAM);
+//! REGION_ALIAS("REGION_STACK", RAM);
+//!
+//! $ edit src/main.rs && cat $_
+//! ```
+//!
+//! ``` ignore,no_run
+//! #![no_std]
+//! #![no_main]
+//!
+//! extern crate panic_halt;
+//!
+//! use riscv_rt::entry;
+//!
+//! // use `main` as the entry point of this application
+//! // `main` is not allowed to return
+//! #[entry]
+//! fn main() -> ! {
+//!     // do something here
+//!     loop { }
+//! }
+//! ```
+//!
+//! ``` text
+//! $ mkdir .cargo && edit .cargo/config && cat $_
+//! [target.riscv32imac-unknown-none-elf]
+//! rustflags = [
+//!   "-C", "link-arg=-Tmemory.x",
+//!   "-C", "link-arg=-Tlink.x",
+//! ]
+//!
+//! [build]
+//! target = "riscv32imac-unknown-none-elf"
+//! $ edit build.rs && cat $_
+//! ```
+//!
+//! ``` ignore,no_run
+//! use std::env;
+//! use std::fs;
+//! use std::path::PathBuf;
+//!
+//! fn main() {
+//!     let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+//!
+//!     // Put the linker script somewhere the linker can find it.
+//!     fs::write(out_dir.join("memory.x"), include_bytes!("memory.x")).unwrap();
+//!     println!("cargo:rustc-link-search={}", out_dir.display());
+//!     println!("cargo:rerun-if-changed=memory.x");
+//!
+//!     println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+//!
+//! ``` text
+//! $ cargo build
+//!
+//! $ riscv32-unknown-elf-objdump -Cd $(find target -name app) | head
+//!
+//! Disassembly of section .text:
+//!
+//! 20000000 <_start>:
+//! 20000000:    800011b7        lui     gp,0x80001
+//! 20000004:    80018193        addi    gp,gp,-2048 # 80000800 <_stack_start+0xffffc800>
+//! 20000008:    80004137        lui     sp,0x80004
+//! ```
+//!
+//! # Symbol interfaces
+//!
+//! This crate makes heavy use of symbols, linker sections and linker scripts to
+//! provide most of its functionality. Below are described the main symbol
+//! interfaces.
+//!
+//! ## `memory.x`
+//!
+//! This file supplies the information about the device to the linker.
+//!
+//! ### `MEMORY`
+//!
+//! The main information that this file must provide is the memory layout of
+//! the device in the form of the `MEMORY` command. The command is documented
+//! [here][2], but at a minimum you'll want to create at least one memory region.
+//!
+//! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html
+//!
+//! To support different relocation models (RAM-only, FLASH+RAM) multiple regions are used:
+//!
+//! - `REGION_TEXT` - for `.init`, `.trap` and `.text` sections
+//! - `REGION_RODATA` - for `.rodata` section and storing initial values for `.data` section
+//! - `REGION_DATA` - for `.data` section
+//! - `REGION_BSS` - for `.bss` section
+//! - `REGION_HEAP` - for the heap area
+//! - `REGION_STACK` - for hart stacks
+//!
+//! Specific aliases for these regions must be defined in `memory.x` file (see example below).
+//!
+//! ### `_stext`
+//!
+//! This symbol provides the loading address of `.text` section. This value can be changed
+//! to override the loading address of the firmware (for example, in case of bootloader present).
+//!
+//! If omitted this symbol value will default to `ORIGIN(REGION_TEXT)`.
+//!
+//! ### `_stack_start`
+//!
+//! This symbol provides the address at which the call stack will be allocated.
+//! The call stack grows downwards so this address is usually set to the highest
+//! valid RAM address plus one (this *is* an invalid address but the processor
+//! will decrement the stack pointer *before* using its value as an address).
+//!
+//! In case of multiple harts present, this address defines the initial stack pointer for hart 0.
+//! Stack pointer for hart `N` is calculated as  `_stack_start - N * _hart_stack_size`.
+//!
+//! If omitted this symbol value will default to `ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)`.
+//!
+//! #### Example
+//!
+//! Allocating the call stack on a different RAM region.
+//!
+//! ``` text
+//! MEMORY
+//! {
+//!   L2_LIM : ORIGIN = 0x08000000, LENGTH = 1M
+//!   RAM : ORIGIN = 0x80000000, LENGTH = 16K
+//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
+//! }
+//!
+//! REGION_ALIAS("REGION_TEXT", FLASH);
+//! REGION_ALIAS("REGION_RODATA", FLASH);
+//! REGION_ALIAS("REGION_DATA", RAM);
+//! REGION_ALIAS("REGION_BSS", RAM);
+//! REGION_ALIAS("REGION_HEAP", RAM);
+//! REGION_ALIAS("REGION_STACK", L2_LIM);
+//!
+//! _stack_start = ORIGIN(L2_LIM) + LENGTH(L2_LIM);
+//! ```
+//!
+//! ### `_max_hart_id`
+//!
+//! This symbol defines the maximum hart id supported. All harts with id
+//! greater than `_max_hart_id` will be redirected to `abort()`.
+//!
+//! This symbol is supposed to be redefined in platform support crates for
+//! multi-core targets.
+//!
+//! If omitted this symbol value will default to 0 (single core).
+//!
+//! ### `_hart_stack_size`
+//!
+//! This symbol defines stack area size for *one* hart.
+//!
+//! If omitted this symbol value will default to 2K.
+//!
+//! ### `_heap_size`
+//!
+//! This symbol provides the size of a heap region. The default value is 0. You can set `_heap_size`
+//! to a non-zero value if you are planning to use heap allocations.
+//!
+//! ### `_sheap`
+//!
+//! This symbol is located in RAM right after the `.bss` and `.data` sections.
+//! You can use the address of this symbol as the start address of a heap
+//! region. This symbol is 4 byte aligned so that address will be a multiple of 4.
+//!
+//! #### Example
+//!
+//! ``` no_run
+//! extern crate some_allocator;
+//!
+//! extern "C" {
+//!     static _sheap: u8;
+//!     static _heap_size: u8;
+//! }
+//!
+//! fn main() {
+//!     unsafe {
+//!         let heap_bottom = &_sheap as *const u8 as usize;
+//!         let heap_size = &_heap_size as *const u8 as usize;
+//!         some_allocator::initialize(heap_bottom, heap_size);
+//!     }
+//! }
+//! ```
+//!
+//! ### `_mp_hook`
+//!
+//! This function is called from all the harts and must return true only for one hart,
+//! which will perform memory initialization. For other harts it must return false
+//! and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
+//! The parameter `hartid` specifies the hartid of the caller.
+//!
+//! This function can be redefined in the following way:
+//!
+//! ``` no_run
+//! #[export_name = "_mp_hook"]
+//! pub extern "Rust" fn mp_hook(hartid: usize) -> bool {
+//!    // ...
+//! }
+//! ```
+//!
+//! Default implementation of this function wakes hart 0 and busy-loops all the other harts.
+//!
+//! ### `ExceptionHandler`
+//!
+//! This function is called when exception is occured. The exception reason can be decoded from the
+//! `mcause`/`scause` register.
+//!
+//! This function can be redefined in the following way:
+//!
+//! ``` no_run
+//! #[export_name = "ExceptionHandler"]
+//! fn custom_exception_handler(trap_frame: &riscv_rt::TrapFrame) -> ! {
+//!     // ...
+//! }
+//! ```
+//! or
+//! ``` no_run
+//! #[no_mangle]
+//! fn ExceptionHandler(trap_frame: &riscv_rt::TrapFrame) -> ! {
+//!     // ...
+//! }
+//! ```
+//!
+//! Default implementation of this function stucks in a busy-loop.
+//!
+//!
+//! ### Core interrupt handlers
+//!
+//! This functions are called when corresponding interrupt is occured.
+//! You can define an interrupt handler with one of the following names:
+//! * `UserSoft`
+//! * `SupervisorSoft`
+//! * `MachineSoft`
+//! * `UserTimer`
+//! * `SupervisorTimer`
+//! * `MachineTimer`
+//! * `UserExternal`
+//! * `SupervisorExternal`
+//! * `MachineExternal`
+//!
+//! For example:
+//! ``` no_run
+//! #[export_name = "MachineTimer"]
+//! fn custom_timer_handler() {
+//!     // ...
+//! }
+//! ```
+//! or
+//! ``` no_run
+//! #[no_mangle]
+//! fn MachineTimer() {
+//!     // ...
+//! }
+//! ```
+//!
+//! If interrupt handler is not explicitly defined, `DefaultHandler` is called.
+//!
+//! ### `DefaultHandler`
+//!
+//! This function is called when interrupt without defined interrupt handler is occured.
+//! The interrupt reason can be decoded from the `mcause`/`scause` register.
+//!
+//! This function can be redefined in the following way:
+//!
+//! ``` no_run
+//! #[export_name = "DefaultHandler"]
+//! fn custom_interrupt_handler() {
+//!     // ...
+//! }
+//! ```
+//! or
+//! ``` no_run
+//! #[no_mangle]
+//! fn DefaultHandler() {
+//!     // ...
+//! }
+//! ```
+//!
+//! Default implementation of this function stucks in a busy-loop.
+//!
+//! # Features
+//!
+//! ## `single-hart`
+//!
+//! This feature saves a little code size if there is only one hart on the target.
+//!
+//! ## `s-mode`
+//!
+//! The supervisor mode feature (`s-mode`) can be activated via [Cargo features](https://doc.rust-lang.org/cargo/reference/features.html).
+//!
+//! For example:
+//! ``` text
+//! [dependencies]
+//! riscv-rt = {features=["s-mode"]}
+//! ```
+//! Internally, riscv-rt uses different versions of precompiled static libraries
+//! for (i) machine mode and (ii) supervisor mode. If the `s-mode` feature was activated,
+//! the build script selects the s-mode library. While most registers/instructions have variants for
+//! both `mcause` and `scause`, the `mhartid` hardware thread register is not available in supervisor
+//! mode. Instead, the hartid is passed as parameter by a bootstrapping firmware (i.e., SBI).
+//!
+//! Use case: QEMU supports [OpenSBI](https://github.com/riscv-software-src/opensbi) as default firmware.
+//! Using the SBI requires riscv-rt to be run in supervisor mode instead of machine mode.
+//! ``` text
+//! APP_BINARY=$(find target -name app)
+//! sudo qemu-system-riscv64 -m 2G -nographic -machine virt -kernel $APP_BINARY
+//! ```
+//! It requires the memory layout to be non-overlapping, like
+//! ``` text
+//! MEMORY
+//! {
+//!   RAM : ORIGIN = 0x80200000, LENGTH = 0x8000000
+//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
+//! }
+//! ```
+
+// NOTE: Adapted from cortex-m/src/lib.rs
+#![no_std]
+#![deny(missing_docs)]
+
+#[cfg(riscv)]
+mod asm;
+
+use core::sync::atomic::{compiler_fence, Ordering};
+
+#[cfg(feature = "s-mode")]
+use riscv::register::{scause as xcause, stvec as xtvec, stvec::TrapMode as xTrapMode};
+
+#[cfg(not(feature = "s-mode"))]
+use riscv::register::{mcause as xcause, mtvec as xtvec, mtvec::TrapMode as xTrapMode};
+
+#[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))]
+use riscv::register::mhartid;
+
+pub use riscv_rt_macros::{entry, pre_init};
+
+#[export_name = "error: riscv-rt appears more than once in the dependency graph"]
+#[doc(hidden)]
+pub static __ONCE__: () = ();
+
+/// Rust entry point (_start_rust)
+///
+/// Zeros bss section, initializes data section and calls main. This function never returns.
+///
+/// # Safety
+///
+/// This function must be called only from assembly `_start` function.
+/// Do **NOT** call this function directly.
+#[link_section = ".init.rust"]
+#[export_name = "_start_rust"]
+pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! {
+    #[rustfmt::skip]
+    extern "Rust" {
+        // This symbol will be provided by the user via `#[entry]`
+        fn main(a0: usize, a1: usize, a2: usize) -> !;
+
+        // This symbol will be provided by the user via `#[pre_init]`
+        fn __pre_init();
+
+        fn _setup_interrupts();
+
+        fn _mp_hook(hartid: usize) -> bool;
+    }
+
+    #[cfg(not(feature = "single-hart"))]
+    let run_init = {
+        // sbi passes hartid as first parameter (a0)
+        #[cfg(feature = "s-mode")]
+        let hartid = a0;
+        #[cfg(not(feature = "s-mode"))]
+        let hartid = mhartid::read();
+
+        _mp_hook(hartid)
+    };
+    #[cfg(feature = "single-hart")]
+    let run_init = true;
+
+    if run_init {
+        __pre_init();
+
+        // Initialize RAM
+        // 1. Copy over .data from flash to RAM
+        // 2. Zero out .bss
+
+        #[cfg(target_arch = "riscv32")]
+        core::arch::asm!(
+            "
+                // Copy over .data
+                la      {start},_sdata
+                la      {end},_edata
+                la      {input},_sidata
+
+            	bgeu    {start},{end},2f
+            1:
+            	lw      {a},0({input})
+            	addi    {input},{input},4
+            	sw      {a},0({start})
+            	addi    {start},{start},4
+            	bltu    {start},{end},1b
+
+            2:
+                li      {a},0
+                li      {input},0
+
+                // Zero out .bss
+            	la      {start},_sbss
+            	la      {end},_ebss
+
+            	bgeu    {start},{end},3f
+            2:
+            	sw      zero,0({start})
+            	addi    {start},{start},4
+            	bltu    {start},{end},2b
+
+            3:
+                li      {start},0
+                li      {end},0
+            ",
+            start = out(reg) _,
+            end = out(reg) _,
+            input = out(reg) _,
+            a = out(reg) _,
+        );
+
+        #[cfg(target_arch = "riscv64")]
+        core::arch::asm!(
+            "
+                // Copy over .data
+                la      {start},_sdata
+                la      {end},_edata
+                la      {input},_sidata
+
+                bgeu    {start},{end},2f
+
+            1: // .data Main Loop
+            	ld      {a},0({input})
+            	addi    {input},{input},8
+            	sd      {a},0({start})
+            	addi    {start},{start},8
+            	bltu    {start},{end},1b
+            
+            2: // .data zero registers
+                li      {a},0
+                li      {input},0
+
+            	la      {start},_sbss
+            	la      {end},_ebss
+            
+                bgeu    {start},{end},4f
+
+            3: // .bss main loop
+            	sd      zero,0({start})
+            	addi    {start},{start},8
+            	bltu    {start},{end},3b
+
+            4: // .bss zero registers
+                //    Zero out used registers
+                li      {start},0
+                li      {end},0
+        ",
+            start = out(reg) _,
+            end = out(reg) _,
+            input = out(reg) _,
+            a = out(reg) _,
+        );
+
+        compiler_fence(Ordering::SeqCst);
+    }
+
+    // TODO: Enable FPU when available
+
+    _setup_interrupts();
+
+    main(a0, a1, a2);
+}
+
+/// Registers saved in trap handler
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug)]
+pub struct TrapFrame {
+    pub ra: usize,
+    pub t0: usize,
+    pub t1: usize,
+    pub t2: usize,
+    pub t3: usize,
+    pub t4: usize,
+    pub t5: usize,
+    pub t6: usize,
+    pub a0: usize,
+    pub a1: usize,
+    pub a2: usize,
+    pub a3: usize,
+    pub a4: usize,
+    pub a5: usize,
+    pub a6: usize,
+    pub a7: usize,
+}
+
+/// Trap entry point rust (_start_trap_rust)
+///
+/// `scause`/`mcause` is read to determine the cause of the trap. XLEN-1 bit indicates
+/// if it's an interrupt or an exception. The result is examined and ExceptionHandler
+/// or one of the core interrupt handlers is called.
+///
+/// # Safety
+///
+/// This function must be called only from assembly `_start_trap` function.
+/// Do **NOT** call this function directly.
+#[link_section = ".trap.rust"]
+#[export_name = "_start_trap_rust"]
+pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) {
+    extern "C" {
+        fn ExceptionHandler(trap_frame: &TrapFrame);
+        fn DefaultHandler();
+    }
+
+    let cause = xcause::read();
+
+    if cause.is_exception() {
+        ExceptionHandler(&*trap_frame)
+    } else if cause.code() < __INTERRUPTS.len() {
+        let h = &__INTERRUPTS[cause.code()];
+        if h.reserved == 0 {
+            DefaultHandler();
+        } else {
+            (h.handler)();
+        }
+    } else {
+        DefaultHandler();
+    }
+}
+
+#[doc(hidden)]
+#[no_mangle]
+#[allow(unused_variables, non_snake_case)]
+pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! {
+    loop {
+        // Prevent this from turning into a UDF instruction
+        // see rust-lang/rust#28728 for details
+        continue;
+    }
+}
+
+#[doc(hidden)]
+#[no_mangle]
+#[allow(unused_variables, non_snake_case)]
+pub fn DefaultInterruptHandler() {
+    loop {
+        // Prevent this from turning into a UDF instruction
+        // see rust-lang/rust#28728 for details
+        continue;
+    }
+}
+
+/* Interrupts */
+#[doc(hidden)]
+pub enum Interrupt {
+    UserSoft,
+    SupervisorSoft,
+    MachineSoft,
+    UserTimer,
+    SupervisorTimer,
+    MachineTimer,
+    UserExternal,
+    SupervisorExternal,
+    MachineExternal,
+}
+
+pub use self::Interrupt as interrupt;
+
+extern "C" {
+    fn UserSoft();
+    fn SupervisorSoft();
+    fn MachineSoft();
+    fn UserTimer();
+    fn SupervisorTimer();
+    fn MachineTimer();
+    fn UserExternal();
+    fn SupervisorExternal();
+    fn MachineExternal();
+}
+
+#[doc(hidden)]
+pub union Vector {
+    pub handler: unsafe extern "C" fn(),
+    pub reserved: usize,
+}
+
+#[doc(hidden)]
+#[no_mangle]
+pub static __INTERRUPTS: [Vector; 12] = [
+    Vector { handler: UserSoft },
+    Vector {
+        handler: SupervisorSoft,
+    },
+    Vector { reserved: 0 },
+    Vector {
+        handler: MachineSoft,
+    },
+    Vector { handler: UserTimer },
+    Vector {
+        handler: SupervisorTimer,
+    },
+    Vector { reserved: 0 },
+    Vector {
+        handler: MachineTimer,
+    },
+    Vector {
+        handler: UserExternal,
+    },
+    Vector {
+        handler: SupervisorExternal,
+    },
+    Vector { reserved: 0 },
+    Vector {
+        handler: MachineExternal,
+    },
+];
+
+#[doc(hidden)]
+#[no_mangle]
+#[rustfmt::skip]
+pub unsafe extern "Rust" fn default_pre_init() {}
+
+#[doc(hidden)]
+#[no_mangle]
+#[rustfmt::skip]
+#[cfg(not(feature = "single-hart"))]
+pub extern "Rust" fn default_mp_hook(hartid: usize) -> bool {
+    match hartid {
+        0 => true,
+        _ => loop {
+            unsafe { riscv::asm::wfi() }
+        },
+    }
+}
+
+/// Default implementation of `_setup_interrupts` that sets `mtvec`/`stvec` to a trap handler address.
+#[doc(hidden)]
+#[no_mangle]
+#[rustfmt::skip]
+pub unsafe extern "Rust" fn default_setup_interrupts() {
+    extern "C" {
+        fn _start_trap();
+    }
+
+    xtvec::write(_start_trap as usize, xTrapMode::Direct);
+}