diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 86541174..25b888f0 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -19,10 +19,12 @@ jobs: filters: | riscv: - 'riscv/**' - riscv-rt: - - 'riscv-rt/**' riscv-pac: - 'riscv-pac/**' + riscv-rt: + - 'riscv-rt/**' + riscv-semihosting: + - 'riscv-semihosting/**' - name: Check for CHANGELOG.md (riscv) if: steps.changes.outputs.riscv == 'true' @@ -32,6 +34,14 @@ jobs: skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv/CHANGELOG.md file.' + - name: Check for CHANGELOG.md (riscv-pac) + if: steps.changes.outputs.riscv-pac == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./riscv-pac/CHANGELOG.md + skipLabels: 'skip changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-pac/CHANGELOG.md file.' + - name: Check for CHANGELOG.md (riscv-rt) if: steps.changes.outputs.riscv-rt == 'true' uses: dangoslen/changelog-enforcer@v3 @@ -40,10 +50,10 @@ jobs: skipLabels: 'skip changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-rt/CHANGELOG.md file.' - - name: Check for CHANGELOG.md (riscv-pac) - if: steps.changes.outputs.riscv-pac == 'true' + - name: Check for CHANGELOG.md (riscv-semihosting) + if: steps.changes.outputs.riscv-semihosting == 'true' uses: dangoslen/changelog-enforcer@v3 with: - changeLogPath: ./riscv-pac/CHANGELOG.md + changeLogPath: ./riscv-semihosting/CHANGELOG.md skipLabels: 'skip changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-pac/CHANGELOG.md file.' + missingUpdateErrorMessage: 'Please add a changelog entry in the riscv-semihosting/CHANGELOG.md file.' diff --git a/.github/workflows/riscv-semihosting.yaml b/.github/workflows/riscv-semihosting.yaml new file mode 100644 index 00000000..4f6da8b0 --- /dev/null +++ b/.github/workflows/riscv-semihosting.yaml @@ -0,0 +1,63 @@ +on: + push: + branches: [ master ] + pull_request: + merge_group: + +name: Build check (riscv-semihosting) + +jobs: + # We check that the crate builds and links for all the toolchains and targets. + build-riscv: + strategy: + matrix: + # All generated code should be running on stable now, MRSV is 1.60.0 + toolchain: [ stable, nightly, 1.60.0 ] + target: + - riscv32i-unknown-none-elf + - riscv32imc-unknown-none-elf + - riscv32imac-unknown-none-elf + - riscv64imac-unknown-none-elf + - riscv64gc-unknown-none-elf + 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@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + targets: ${{ matrix.target }} + - name: Build (M-mode) + run: cargo build --package riscv-semihosting --target ${{ matrix.target }} + - name: Build (U-mode) + run: cargo build --package riscv-semihosting --target ${{ matrix.target }} --features=u-mode + - name: Build (no semihosting) + run: cargo build --package riscv-semihosting --target ${{ matrix.target }} --features=no-semihosting + + # On MacOS, Ubuntu, and Windows, we at least make sure that the crate builds and links. + build-others: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - name: Build (no features) + run: cargo build --package riscv-semihosting + - name: Build (all features) + run: cargo build --package riscv-semihosting --all-features + + # Job to check that all the builds succeeded + build-check: + needs: + - build-riscv + - build-others + runs-on: ubuntu-latest + if: always() + steps: + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/Cargo.toml b/Cargo.toml index b97d6de3..fe8fdd1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ members = [ "riscv", "riscv-pac", "riscv-rt", + "riscv-semihosting", ] diff --git a/README.md b/README.md index d694f26c..f35a5e50 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository contains various crates useful for writing Rust programs on RISC * [`riscv`]: CPU registers access and intrinsics * [`riscv-pac`]: Common traits to be implemented by RISC-V PACs * [`riscv-rt`]: Startup code and interrupt handling - +* [`riscv-semihosting`]: Semihosting for RISC-V processors This project is developed and maintained by the [RISC-V team][team]. @@ -22,6 +22,8 @@ Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises to intervene to uphold that code of conduct. [`riscv`]: https://crates.io/crates/riscv +[`riscv-pac`]: https://crates.io/crates/riscv-pac [`riscv-rt`]: https://crates.io/crates/riscv-rt +[`riscv-semihosting`]: https://crates.io/crates/riscv-semihosting [team]: https://github.com/rust-embedded/wg#the-risc-v-team [CoC]: CODE_OF_CONDUCT.md diff --git a/riscv-semihosting/CHANGELOG.md b/riscv-semihosting/CHANGELOG.md new file mode 100644 index 00000000..df76aa4f --- /dev/null +++ b/riscv-semihosting/CHANGELOG.md @@ -0,0 +1,33 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +- Moved to the `riscv` Cargo workspace +- Bring in API changes from + [cortex-m-semihosting](https://github.com/rust-embedded/cortex-m/tree/master/cortex-m-semihosting), + including: + - Addition of the `hprint`, `hprintln`, `heprint`, `heprintln`, and `dbg` + macros. + - `hprint` and `heprintln` print to host stdout without and with a + newline, respectively. + - `heprint` and `heprintln` do the same, except to host stderr. + - `dbg` works exactly like + [`std::dbg`](https://doc.rust-lang.org/std/macro.dbg.html). + - `HStdout` and `HStderr` have been combined into `HostStream`. + - `inline-asm` feature removed, switched to stabilized inline asm and MSRV + bumped to 1.59.0 +- Clean up documentation, removing unnecessary references to + cortex-m-semihosting and improving clarity. +- Added GitHub Actions CI +- Add features to select the privilege level the semihosting operations will be + started from + +## [v0.0.1] - 2018-02-27 + +- Initial release + +[Unreleased]: https://github.com/riscv-rust/riscv-semihosting/compare/cb1afe4002d576b87bfd4c199f42a43815984ce4..HEAD +[v0.0.1]: https://github.com/riscv-rust/riscv-semihosting/tree/cb1afe4002d576b87bfd4c199f42a43815984ce4 diff --git a/riscv-semihosting/Cargo.toml b/riscv-semihosting/Cargo.toml new file mode 100644 index 00000000..4f483433 --- /dev/null +++ b/riscv-semihosting/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors = [ + "The Cortex-M Team ", + "Jorge Aparicio ", + "The RISC-V Team ", +] +description = "Semihosting for RISCV processors" +documentation = "https://docs.rs/riscv-semihosting" +keywords = ["semihosting", "riscv"] +categories = ["no-std", "embedded"] +license = "MIT OR Apache-2.0" +name = "riscv-semihosting" +readme = "README.md" +repository = "https://github.com/riscv-rust/riscv" +version = "0.0.1" +edition = "2021" +rust-version = "1.60.0" + +[features] +u-mode = [] +jlink-quirks = [] +no-semihosting = [] +default = ["jlink-quirks"] + +[dependencies] +critical-section = "1.0.0" +riscv = {path = "../riscv", version = "0.10.1"} diff --git a/riscv-semihosting/README.md b/riscv-semihosting/README.md new file mode 100644 index 00000000..75da8aeb --- /dev/null +++ b/riscv-semihosting/README.md @@ -0,0 +1,61 @@ +[![crates.io](https://img.shields.io/crates/d/riscv-semihosting.svg)](https://crates.io/crates/riscv-semihosting) +[![crates.io](https://img.shields.io/crates/v/riscv-semihosting.svg)](https://crates.io/crates/riscv-semihosting) + +# `riscv-semihosting` + +> Semihosting for RISC-V processors + +This is a fork of the +[cortex-m-semihosting](https://docs.rs/cortex-m-semihosting) crate with changes +to support the RISC-V Semihosting Specification as documented +[here](https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc) + +This crate can (almost) be used in exactly the same way as cortex-m-semihosting, +simply by changing calls to `cortex_m_semihosting::*` to `riscv_semihosting::*`. +Given this, the +[`cortex-m-semihosting documentation`](https://docs.rs/cortex-m-semihosting) is +generally sufficient for using this library. + +A major difference between this library and `cortex-m-semihosting` is that there +are mandatory features to choose the privilege level at which the semihosting +calls are executed. The *machine-mode (M-mode)* feature will cause the macros in `export` +to execute the semihosting operation in an interrupt-free context, while +*user-mode (U-mode)* causes them to just execute the operation. +By default, M-mode is used. You can activate the U-mode via the `u-mode` feature. + + +# Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.60.0 and up. It **won't** +compile with older versions. + +## License + +Copyright 2018-2023 [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. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: ../CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-risc-v-team diff --git a/riscv-semihosting/build.rs b/riscv-semihosting/build.rs new file mode 100644 index 00000000..298364fd --- /dev/null +++ b/riscv-semihosting/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("riscv") { + println!("cargo:rustc-cfg=riscv"); + } +} diff --git a/riscv-semihosting/src/debug.rs b/riscv-semihosting/src/debug.rs new file mode 100644 index 00000000..1e2a7b06 --- /dev/null +++ b/riscv-semihosting/src/debug.rs @@ -0,0 +1,94 @@ +//! Interacting with debugging agent +//! +//! # Example +//! +//! This example will show how to terminate the QEMU session. The program +//! should be running under QEMU with semihosting enabled +//! (use `-semihosting` flag). +//! +//! Target program: +//! +//! ```no_run +//! use riscv_semihosting::debug::{self, EXIT_SUCCESS, EXIT_FAILURE}; +//! +//! if 2 == 2 { +//! // report success +//! debug::exit(EXIT_SUCCESS); +//! } else { +//! // report failure +//! debug::exit(EXIT_FAILURE); +//! } +//!``` + +/// This values are taken from section 5.5.2 of +/// ADS Debug Target Guide (DUI0058). +// TODO document +#[allow(missing_docs)] +pub enum Exception { + // Hardware reason codes + BranchThroughZero = 0x20000, + UndefinedInstr = 0x20001, + SoftwareInterrupt = 0x20002, + PrefetchAbort = 0x20003, + DataAbort = 0x20004, + AddressException = 0x20005, + IRQ = 0x20006, + FIQ = 0x20007, + // Software reason codes + BreakPoint = 0x20020, + WatchPoint = 0x20021, + StepComplete = 0x20022, + RunTimeErrorUnknown = 0x20023, + InternalError = 0x20024, + UserInterruption = 0x20025, + ApplicationExit = 0x20026, + StackOverflow = 0x20027, + DivisionByZero = 0x20028, + OSSpecific = 0x20029, +} + +/// Status enum for `exit` syscall. +pub type ExitStatus = Result<(), ()>; + +/// Successful execution of a program. +pub const EXIT_SUCCESS: ExitStatus = Ok(()); + +/// Unsuccessful execution of a program. +pub const EXIT_FAILURE: ExitStatus = Err(()); + +/// Reports to the debugger that the execution has completed. +/// +/// This call can be used to terminate QEMU session and report back success +/// or failure. If you need to pass more than one type of error, consider +/// using `report_exception` syscall instead. +/// +/// This call should not return. However, it is possible for the debugger +/// to request that the application continue. In that case this call +/// returns normally. +/// +pub fn exit(status: ExitStatus) { + match status { + EXIT_SUCCESS => report_exception(Exception::ApplicationExit), + EXIT_FAILURE => report_exception(Exception::RunTimeErrorUnknown), + } +} + +/// Report an exception to the debugger directly. +/// +/// Exception handlers can use this SWI at the end of handler chains +/// as the default action, to indicate that the exception has not been handled. +/// +/// This call should not return. However, it is possible for the debugger +/// to request that the application continue. In that case this call +/// returns normally. +/// +/// # Arguments +/// +/// * `reason` - A reason code reported back to the debugger. +/// +pub fn report_exception(reason: Exception) { + let code = reason as usize; + unsafe { + syscall1!(REPORT_EXCEPTION, code); + } +} diff --git a/riscv-semihosting/src/export.rs b/riscv-semihosting/src/export.rs new file mode 100644 index 00000000..c56d6d48 --- /dev/null +++ b/riscv-semihosting/src/export.rs @@ -0,0 +1,101 @@ +//! IMPLEMENTATION DETAILS USED BY MACROS + +use crate::hio::{self, HostStream}; +use core::fmt::{self, Write}; + +static mut HSTDOUT: Option = None; +static mut HSTDERR: Option = None; + +#[cfg(not(feature = "u-mode"))] +mod machine { + use super::*; + + pub fn hstdout_str(s: &str) { + let _result = critical_section::with(|_| unsafe { + if HSTDOUT.is_none() { + HSTDOUT = Some(hio::hstdout()?); + } + + HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + }); + } + + pub fn hstdout_fmt(args: fmt::Arguments) { + let _result = critical_section::with(|_| unsafe { + if HSTDOUT.is_none() { + HSTDOUT = Some(hio::hstdout()?); + } + + HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + }); + } + + pub fn hstderr_str(s: &str) { + let _result = critical_section::with(|_| unsafe { + if HSTDERR.is_none() { + HSTDERR = Some(hio::hstderr()?); + } + + HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + }); + } + + pub fn hstderr_fmt(args: fmt::Arguments) { + let _result = critical_section::with(|_| unsafe { + if HSTDERR.is_none() { + HSTDERR = Some(hio::hstderr()?); + } + + HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + }); + } +} +#[cfg(not(feature = "u-mode"))] +pub use machine::*; + +#[cfg(feature = "u-mode")] +mod user { + use super::*; + + pub fn hstdout_str(s: &str) { + let _result = unsafe { + if HSTDOUT.is_none() { + HSTDOUT = Some(hio::hstdout().unwrap()); + } + + HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + }; + } + + pub fn hstdout_fmt(args: fmt::Arguments) { + let _result = unsafe { + if HSTDOUT.is_none() { + HSTDOUT = Some(hio::hstdout().unwrap()); + } + + HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + }; + } + + pub fn hstderr_str(s: &str) { + let _result = unsafe { + if HSTDERR.is_none() { + HSTDERR = Some(hio::hstderr().unwrap()); + } + + HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + }; + } + + pub fn hstderr_fmt(args: fmt::Arguments) { + let _result = unsafe { + if HSTDERR.is_none() { + HSTDERR = Some(hio::hstderr().unwrap()); + } + + HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + }; + } +} +#[cfg(feature = "u-mode")] +pub use user::*; diff --git a/riscv-semihosting/src/hio.rs b/riscv-semihosting/src/hio.rs new file mode 100644 index 00000000..e0614ad3 --- /dev/null +++ b/riscv-semihosting/src/hio.rs @@ -0,0 +1,69 @@ +//! Host I/O + +// Fixing this lint requires a breaking change that does not add much value +#![allow(clippy::result_unit_err)] + +use crate::nr; +use core::{fmt, slice}; + +/// A byte stream to the host (e.g., host's stdout or stderr). +#[derive(Clone, Copy)] +pub struct HostStream { + fd: usize, +} + +impl HostStream { + /// Attempts to write an entire `buffer` into this sink + pub fn write_all(&mut self, buffer: &[u8]) -> Result<(), ()> { + write_all(self.fd, buffer) + } +} + +impl fmt::Write for HostStream { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_all(s.as_bytes()).map_err(|_| fmt::Error) + } +} + +/// Construct a new handle to the host's standard error. +pub fn hstderr() -> Result { + // There is actually no stderr access in ARM Semihosting documentation. Use + // convention used in libgloss. + // See: libgloss/arm/syscalls.c, line 139. + // https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=libgloss/arm/syscalls.c#l139 + open(":tt\0", nr::open::W_APPEND) +} + +/// Construct a new handle to the host's standard output. +pub fn hstdout() -> Result { + open(":tt\0", nr::open::W_TRUNC) +} + +fn open(name: &str, mode: usize) -> Result { + let name = name.as_bytes(); + match unsafe { syscall!(OPEN, name.as_ptr(), mode, name.len() - 1) } as isize { + -1 => Err(()), + fd => Ok(HostStream { fd: fd as usize }), + } +} + +fn write_all(fd: usize, mut buffer: &[u8]) -> Result<(), ()> { + while !buffer.is_empty() { + match unsafe { syscall!(WRITE, fd, buffer.as_ptr(), buffer.len()) } { + // Done + 0 => return Ok(()), + // `n` bytes were not written + n if n <= buffer.len() => { + let offset = (buffer.len() - n) as isize; + buffer = unsafe { slice::from_raw_parts(buffer.as_ptr().offset(offset), n) } + } + #[cfg(feature = "jlink-quirks")] + // Error (-1) - should be an error but JLink can return -1, -2, -3,... + // For good measure, we allow up to negative 15. + n if n > 0xfffffff0 => return Ok(()), + // Error + _ => return Err(()), + } + } + Ok(()) +} diff --git a/riscv-semihosting/src/lib.rs b/riscv-semihosting/src/lib.rs new file mode 100644 index 00000000..9c019e18 --- /dev/null +++ b/riscv-semihosting/src/lib.rs @@ -0,0 +1,241 @@ +//! Semihosting for RISCV processors +//! +//! # What is semihosting? +//! +//! "Semihosting is a technique where an application running in a debug or +//! simulation environment can access elements of the system hosting the +//! debugger or simulator including console, file system, time and other +//! functions. This allows for diagnostics, interaction and measurement of a +//! target system without requiring significant infrastructure to exist in that +//! target environment." - RISC-V Semihosting Spec +//! +//! # Interface +//! +//! This crate provides implementations of +//! [`core::fmt::Write`](https://doc.rust-lang.org/core/fmt/trait.Write.html), +//! so you can use it, in conjunction with +//! [`core::format_args!`](https://doc.rust-lang.org/core/macro.format_args.html) +//! or the [`write!` macro](https://doc.rust-lang.org/core/macro.write.html), +//! for user-friendly construction and printing of formatted strings. +//! +//! Since semihosting operations are modeled as [system calls][sc], this crate +//! exposes an untyped `syscall!` interface just like the [`sc`] crate does. +//! +//! [sc]: https://en.wikipedia.org/wiki/System_call +//! [`sc`]: https://crates.io/crates/sc +//! +//! # Forewarning +//! +//! Semihosting operations are *very* slow. Like, each WRITE operation can take +//! hundreds of milliseconds. +//! +//! # Example +//! +//! ## Using `hio::hstdout` +//! +//! This example will demonstrate how to print formatted strings. +//! +//! ```no_run +//! use riscv_semihosting::hio; +//! use core::fmt::Write; +//! +//! // This function will be called by the application +//! fn print() -> Result<(), core::fmt::Error> { +//! let mut stdout = hio::hstdout().map_err(|_| core::fmt::Error)?; +//! let language = "Rust"; +//! let ranking = 1; +//! +//! write!(stdout, "{} on embedded is #{}!", language, ranking)?; +//! +//! Ok(()) +//! } +//! ``` +//! +//! On the host side: +//! +//! ``` text +//! $ openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log +//! Open On-Chip Debugger 0.9.0 (2016-04-27-23:18) +//! Licensed under GNU GPL v2 +//! For bug reports, read +//! http://openocd.org/doc/doxygen/bugs.html +//! # the command will block at this point +//! ``` +//! +//! The OpenOCD logs will be redirected to `/tmp/openocd.log`. You can view +//! those logs in "real time" using `tail` +//! +//! ``` text +//! $ tail -f /tmp/openocd.log +//! Info : Unable to match requested speed 1000 kHz, using 950 kHz +//! Info : Unable to match requested speed 1000 kHz, using 950 kHz +//! Info : clock speed 950 kHz +//! Info : STLINK v1 JTAG v11 API v2 SWIM v0 VID 0x0483 PID 0x3744 +//! Info : using stlink api v2 +//! Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints +//! ``` +//! +//! Alternatively you could omit the `-l` flag from the `openocd` call, and the +//! `tail -f` command but the OpenOCD output will have intermingled in it logs +//! from its normal operation. +//! +//! Then, we run the program: +//! +//! ``` text +//! $ arm-none-eabi-gdb hello-world +//! (gdb) # Connect to OpenOCD +//! (gdb) target remote :3333 +//! +//! (gdb) # Enable OpenOCD's semihosting support +//! (gdb) monitor arm semihosting enable +//! +//! (gdb) # Flash the program +//! (gdb) load +//! +//! (gdb) # Run the program +//! (gdb) continue +//! ``` +//! +//! And you'll see the output under OpenOCD's terminal +//! +//! ``` text +//! # openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log +//! (..) +//! Rust on embedded is #1! +//! ``` +//! ## Using the syscall interface +//! +//! This example will show how to print "Hello, world!" on the host. +//! +//! Target program: +//! +//! ```no_run +//! use riscv_semihosting::syscall; +//! +//! // This function will be called by the application +//! fn print() { +//! // File descriptor (on the host) +//! const STDOUT: usize = 1; // NOTE the host stdout may not always be fd 1 +//! static MSG: &'static [u8] = b"Hello, world!\n"; +//! +//! // Signature: fn write(fd: usize, ptr: *const u8, len: usize) -> usize +//! let r = unsafe { syscall!(WRITE, STDOUT, MSG.as_ptr(), MSG.len()) }; +//! } +//! ``` +//! Output and monitoring proceed as in the above example. +//! +//! ## The `dbg!` macro +//! +//! Analogous to [`std::dbg`](https://doc.rust-lang.org/std/macro.dbg.html) the +//! macro `dbg!` returns a given expression and prints it using `heprintln!` +//! including context for quick and dirty debugging. +//! +//! Panics if `heprintln!` returns an error. +//! +//! Example: +//! +//! ```no_run +//! const UUID: *mut u32 = 0x0009_FC70 as *mut u32; +//! dbg!(UUID); +//! let mut uuid: [u32; 4] = [0; 4]; +//! for i in 0..4 { +//! dbg!(i); +//! uuid[i] = unsafe { dbg!(UUID.offset(i as isize).read_volatile()) }; +//! } +//! ``` +//! outputs +//! ```text +//! [examples/semihosting.rs:37] UUID = 0x0009fc70 +//! [examples/semihosting.rs:40] i = 0 +//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 3370045464 +//! [examples/semihosting.rs:40] i = 1 +//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1426218275 +//! [examples/semihosting.rs:40] i = 2 +//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 2422621116 +//! [examples/semihosting.rs:40] i = 3 +//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1044138593 +//! ``` +//! +//! # Optional features +//! +//! ## `jlink-quirks` +//! +//! When this feature is enabled, return values above `0xfffffff0` from +//! semihosting operation `SYS_WRITE` (0x05) are interpreted as if the entire +//! buffer had been written. The current latest version 6.48b of J-Link exhibits +//! such behaviour, causing a panic if this feature is not enabled. +//! +//! ## `no-semihosting` +//! +//! When this feature is enabled, the underlying system calls are patched out. +//! +//! # Reference +//! +//! For documentation about the semihosting operations, check +//! ['Semihosting for AArch32 and AArch64'](https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst). +//! The RISC-V Semihosting spec is identical to Arm's with the exception of the +//! assembly sequence necessary to trigger a semihosting call, so their +//! documentation is sufficient. + +#![deny(missing_docs)] +#![no_std] + +#[cfg(all(riscv, not(feature = "no-semihosting")))] +use core::arch::asm; + +#[macro_use] +mod macros; + +pub mod debug; +#[doc(hidden)] +pub mod export; +pub mod hio; +pub mod nr; + +/// Performs a semihosting operation, takes a pointer to an argument block +/// +/// # Safety +/// +/// The syscall number must be a valid [semihosting operation], +/// and the arguments must be valid for the associated operation. +/// +/// [semihosting operation]: https://developer.arm.com/documentation/dui0471/i/semihosting/semihosting-operations?lang=en +#[inline(always)] +pub unsafe fn syscall(nr: usize, arg: &T) -> usize { + syscall1(nr, arg as *const T as usize) +} + +/// Performs a semihosting operation, takes one integer as an argument +/// +/// # Safety +/// +/// Same as [`syscall`]. +#[inline(always)] +pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize { + match () { + #[cfg(all(riscv, not(feature = "no-semihosting")))] + () => { + let mut nr = _nr; + // The instructions below must always be uncompressed, otherwise + // it will be treated as a regular break, hence the norvc option. + // + // See https://github.com/riscv/riscv-semihosting-spec for more details. + asm!(" + .option push + .option norvc + slli x0, x0, 0x1f + ebreak + srai x0, x0, 0x7 + .option pop + ", + inout("a0") nr, + in("a1") _arg, + ); + nr + } + #[cfg(all(riscv, feature = "no-semihosting"))] + () => 0, + #[cfg(not(riscv))] + () => unimplemented!(), + } +} diff --git a/riscv-semihosting/src/macros.rs b/riscv-semihosting/src/macros.rs new file mode 100644 index 00000000..f1cc4f3e --- /dev/null +++ b/riscv-semihosting/src/macros.rs @@ -0,0 +1,120 @@ +/// Variable argument version of `syscall` +#[macro_export] +macro_rules! syscall { + ($nr:ident) => { + $crate::syscall1($crate::nr::$nr, 0) + }; + ($nr:ident, $a1:expr) => { + $crate::syscall($crate::nr::$nr, &[$a1 as usize]) + }; + ($nr:ident, $a1:expr, $a2:expr) => { + $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize]) + }; + ($nr:ident, $a1:expr, $a2:expr, $a3:expr) => { + $crate::syscall($crate::nr::$nr, &[$a1 as usize, $a2 as usize, $a3 as usize]) + }; + ($nr:ident, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => { + $crate::syscall( + $crate::nr::$nr, + &[$a1 as usize, $a2 as usize, $a3 as usize, $a4 as usize], + ) + }; +} + +/// Macro version of `syscall1`. +#[macro_export] +macro_rules! syscall1 { + ($nr:ident, $a1:expr) => { + $crate::syscall1($crate::nr::$nr, $a1 as usize) + }; +} + +/// Macro for printing to the HOST standard output. +/// +/// This is similar to the `print!` macro in the standard library. Both will panic on any failure to +/// print. +#[macro_export] +macro_rules! hprint { + ($s:expr) => { + $crate::export::hstdout_str($s) + }; + ($($tt:tt)*) => { + $crate::export::hstdout_fmt(format_args!($($tt)*)) + }; +} + +/// Macro for printing to the HOST standard output, with a newline. +/// +/// This is similar to the `println!` macro in the standard library. Both will panic on any failure to +/// print. +#[macro_export] +macro_rules! hprintln { + () => { + $crate::export::hstdout_str("\n") + }; + ($s:expr) => { + $crate::export::hstdout_str(concat!($s, "\n")) + }; + ($s:expr, $($tt:tt)*) => { + $crate::export::hstdout_fmt(format_args!(concat!($s, "\n"), $($tt)*)) + }; +} + +/// Macro for printing to the HOST standard error. +/// +/// This is similar to the `eprint!` macro in the standard library. Both will panic on any failure +/// to print. +#[macro_export] +macro_rules! heprint { + ($s:expr) => { + $crate::export::hstderr_str($s) + }; + ($($tt:tt)*) => { + $crate::export::hstderr_fmt(format_args!($($tt)*)) + }; +} + +/// Macro for printing to the HOST standard error, with a newline. +/// +/// This is similar to the `eprintln!` macro in the standard library. Both will panic on any failure +/// to print. +#[macro_export] +macro_rules! heprintln { + () => { + $crate::export::hstderr_str("\n") + }; + ($s:expr) => { + $crate::export::hstderr_str(concat!($s, "\n")) + }; + ($s:expr, $($tt:tt)*) => { + $crate::export::hstderr_fmt(format_args!(concat!($s, "\n"), $($tt)*)) + }; +} + +/// Macro that prints and returns the value of a given expression for quick and +/// dirty debugging. +/// +/// Works exactly like `dbg!` in the standard library, replacing `eprintln!` +/// with `heprintln!`. +#[macro_export] +macro_rules! dbg { + () => { + $crate::heprintln!("[{}:{}]", file!(), line!()); + }; + ($val:expr) => { + // Use of `match` here is intentional because it affects the lifetimes + // of temporaries - https://stackoverflow.com/a/48732525/1063961 + match $val { + tmp => { + $crate::heprintln!("[{}:{}] {} = {:#?}", + file!(), line!(), stringify!($val), &tmp); + tmp + } + } + }; + // Trailing comma with single argument is ignored + ($val:expr,) => { $crate::dbg!($val) }; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} diff --git a/riscv-semihosting/src/nr.rs b/riscv-semihosting/src/nr.rs new file mode 100644 index 00000000..45c3b808 --- /dev/null +++ b/riscv-semihosting/src/nr.rs @@ -0,0 +1,61 @@ +//! Semihosting operations +//! +//! The details of what each operation does can be found in the +//! [ARM Semihosting Specification](https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#semihosting-operations). +//! The RISC-V Semihosting operations are identiacal to ARM's, so their +//! documentation is sufficient. + +#![allow(missing_docs)] + +pub const CLOCK: usize = 0x10; +pub const CLOSE: usize = 0x02; +pub const ELAPSED: usize = 0x30; +pub const ERRNO: usize = 0x13; +pub const FLEN: usize = 0x0c; +pub const GET_CMDLINE: usize = 0x15; +pub const HEAPINFO: usize = 0x16; +pub const ISERROR: usize = 0x08; +pub const ISTTY: usize = 0x09; +pub const OPEN: usize = 0x01; +pub const READ: usize = 0x06; +pub const READC: usize = 0x07; +pub const REMOVE: usize = 0x0e; +pub const RENAME: usize = 0x0f; +pub const SEEK: usize = 0x0a; +pub const SYSTEM: usize = 0x12; +pub const TICKFREQ: usize = 0x31; +pub const TIME: usize = 0x11; +pub const TMPNAM: usize = 0x0d; +pub const WRITE0: usize = 0x04; +pub const WRITE: usize = 0x05; +pub const WRITEC: usize = 0x03; +pub const ENTER_SVC: usize = 0x17; +pub const REPORT_EXCEPTION: usize = 0x18; + +/// Values for the mode parameter of the OPEN syscall. +pub mod open { + /// Mode corresponding to fopen "r" mode. + pub const R: usize = 0; + /// Mode corresponding to fopen "rb" mode. + pub const R_BINARY: usize = 1; + /// Mode corresponding to fopen "r+" mode. + pub const RW: usize = 2; + /// Mode corresponding to fopen "r+b" mode. + pub const RW_BINARY: usize = 3; + /// Mode corresponding to fopen "w" mode. + pub const W_TRUNC: usize = 4; + /// Mode corresponding to fopen "wb" mode. + pub const W_TRUNC_BINARY: usize = 5; + /// Mode corresponding to fopen "w+" mode. + pub const RW_TRUNC: usize = 6; + /// Mode corresponding to fopen "w+b" mode. + pub const RW_TRUNC_BINARY: usize = 7; + /// Mode corresponding to fopen "a" mode. + pub const W_APPEND: usize = 8; + /// Mode corresponding to fopen "ab" mode. + pub const W_APPEND_BINARY: usize = 9; + /// Mode corresponding to fopen "a+" mode. + pub const RW_APPEND: usize = 10; + /// Mode corresponding to fopen "a+b" mode. + pub const RW_APPEND_BINARY: usize = 11; +}