diff options
author | 2020-10-13 23:32:15 +0000 | |
---|---|---|
committer | 2020-10-13 23:32:15 +0000 | |
commit | 432e5f527430394570d1d07454e26cc41d5b2936 (patch) | |
tree | d3cde25a18eabfc5f0342dbf2d31ab767f21bab9 /cortex-m-semihosting | |
parent | f77d64a2d1505335e4a170d03a40993bb066fd02 (diff) | |
parent | b51178fae6373d8dae95f2fb661e0635359e8bc0 (diff) | |
download | cortex-m-432e5f527430394570d1d07454e26cc41d5b2936.tar.gz cortex-m-432e5f527430394570d1d07454e26cc41d5b2936.tar.zst cortex-m-432e5f527430394570d1d07454e26cc41d5b2936.zip |
Merge #263p-sh-v0.5.3c-m-sh-v0.3.5
263: Import cortex-m-semihosting and panic-semihosting into this repo r=adamgreig a=jonas-schievink
Motivation:
* Allows writing QEMU tests for `cortex-m`'s functionality that use semihosting to control QEMU. Previously these crates would pull in another cortex-m version, which doesn't work. Now they have a `path` dependency on the root crate.
* Lets us share the outline-inline-assembly setup and `cargo-xtask` in general.
* Lets us share CI and bot setup between more crates.
* 2 fewer repos to triage and keep track of (I'll transfer their issues after this is merged).
I also want to import cortex-m-rt, but I'll do that in a later PR.
CI was updated to build-test all crates with all or most feature combinations, like it did before.
Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
Diffstat (limited to 'cortex-m-semihosting')
-rw-r--r-- | cortex-m-semihosting/CHANGELOG.md | 123 | ||||
-rw-r--r-- | cortex-m-semihosting/Cargo.toml | 22 | ||||
-rw-r--r-- | cortex-m-semihosting/README.md | 40 | ||||
-rw-r--r-- | cortex-m-semihosting/build.rs | 23 | ||||
-rw-r--r-- | cortex-m-semihosting/src/debug.rs | 96 | ||||
-rw-r--r-- | cortex-m-semihosting/src/export.rs | 51 | ||||
-rw-r--r-- | cortex-m-semihosting/src/hio.rs | 88 | ||||
-rw-r--r-- | cortex-m-semihosting/src/lib.rs | 232 | ||||
-rw-r--r-- | cortex-m-semihosting/src/macros.rs | 119 | ||||
-rw-r--r-- | cortex-m-semihosting/src/nr.rs | 57 |
10 files changed, 851 insertions, 0 deletions
diff --git a/cortex-m-semihosting/CHANGELOG.md b/cortex-m-semihosting/CHANGELOG.md new file mode 100644 index 0000000..f813f8b --- /dev/null +++ b/cortex-m-semihosting/CHANGELOG.md @@ -0,0 +1,123 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +## [v0.3.5] - 2019-08-29 + +### Added + +- Adds a feature to work around JLink quirks +- Adds a dbg! macro using heprintln +- Added thumbv8m.main support on stable + +### Fixed + +- Now Rust 2018 edition + +## [v0.3.4] - 2019-08-13 + +### Fixed + +- Support for thumbv8 mainline hf target + +## [v0.3.3] - 2019-04-22 + +### Added + +- Adds support for thumbv8 and cortex-m v0.6.0 + +## [v0.3.2] - 2018-11-04 + +### Added + +- Added a family of `hprint` macros for printing to the host standard output / + error via globally shared `HStdout` / `HStderr` handles . + +## [v0.3.1] - 2018-08-27 + +### Changed + +- This crate no longer depends on `arm-none-eabi-gcc`. + +## [v0.3.0] - 2018-05-10 + +### Changed + +- [breaking-change] `inline-asm` is no longer a default feature (i.e. a feature that's enabled by + default). The consequence is that this crate now compiles on 1.27 (beta) by default, and opting + into `inline-asm` requires nightly. + +## [v0.2.1] - 2018-04-25 + +### Added + +- An opt-out "inline-asm" Cargo feature. When this feature is disabled semihosting is implemented + using an external assembly file instead of using the unstable inline assembly (`asm!`) feature + meaning that this crate can be compiled on stable. + +## [v0.2.0] - 2017-07-07 + +### Added + +- `exit` and `report_exception` syscalls + +- `HStdout` and `HStderr` structs that represent handles to the host stdout and + stderr stream respectively. + +### Changed + +- [breaking-change] The `io` module has been renamed to `hio` to reflect that + this is I/O *on the host*. + +### Removed + +- [breaking-change] the family of `write` functions in the `io` module. Instead + use `HStdout` / `HStderr` and its `write_all` method and `fmt::Write` + implementation. + +- [breaking-change] the `hprint!` family of macros. Instead use `HStdout` and + the standard `write!` macro. + +## [v0.1.3] - 2017-02-27 + +### Added + +- A family of `ewrite` functions and `ehprint!` macros to write to the host's + stderr. + +### Fixed + +- `write_all` logic when a single write doesn't write all the buffer bytes + +## [v0.1.2] - 2017-02-15 + +### Fixed + +- the `hprintln!` macro when called without arguments. + +## [v0.1.1] - 2017-01-22 + +### Added + +- Expose a family of `write` functions to write to the host's stdout without + going through the `hprint!` macros. + +## v0.1.0 - 2017-01-22 + +- Initial release + +[Unreleased]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.5...HEAD +[v0.3.5]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.4...v0.3.5 +[v0.3.4]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.3...v0.3.4 +[v0.3.3]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.2...v0.3.3 +[v0.3.2]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.1...v0.3.2 +[v0.3.1]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.0...v0.3.1 +[v0.3.0]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.2.1...v0.3.0 +[v0.2.1]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.3...v0.2.0 +[v0.1.3]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.2...v0.1.3 +[v0.1.2]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.1...v0.1.2 +[v0.1.1]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.1.0...v0.1.1 diff --git a/cortex-m-semihosting/Cargo.toml b/cortex-m-semihosting/Cargo.toml new file mode 100644 index 0000000..8eb28b0 --- /dev/null +++ b/cortex-m-semihosting/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = [ + "The Cortex-M Team <cortex-m@teams.rust-embedded.org>", + "Jorge Aparicio <japaricious@gmail.com>", +] +description = "Semihosting for ARM Cortex-M processors" +documentation = "https://docs.rs/cortex-m-semihosting" +keywords = ["semihosting", "arm", "cortex-m"] +license = "MIT OR Apache-2.0" +name = "cortex-m-semihosting" +readme = "README.md" +repository = "https://github.com/rust-embedded/cortex-m" +version = "0.3.5" +edition = "2018" + +[features] +inline-asm = [] +jlink-quirks = [] +no-semihosting = [] + +[dependencies] +cortex-m = { path = "..", version = ">= 0.5.8, < 0.7" } diff --git a/cortex-m-semihosting/README.md b/cortex-m-semihosting/README.md new file mode 100644 index 0000000..bfbfb44 --- /dev/null +++ b/cortex-m-semihosting/README.md @@ -0,0 +1,40 @@ +[](https://crates.io/crates/cortex-m-semihosting) +[](https://crates.io/crates/cortex-m-semihosting) + +# `cortex-m-semihosting` + +> Semihosting for ARM Cortex-M processors + +This project is developed and maintained by the [Cortex-M team][team]. + +## [Documentation](https://docs.rs/cortex-m-semihosting) + +# Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.33.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### 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 [Cortex-M team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: ../CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-cortex-m-team diff --git a/cortex-m-semihosting/build.rs b/cortex-m-semihosting/build.rs new file mode 100644 index 0000000..5fc6a04 --- /dev/null +++ b/cortex-m-semihosting/build.rs @@ -0,0 +1,23 @@ +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + let target = env::var("TARGET").unwrap(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let name = env::var("CARGO_PKG_NAME").unwrap(); + + if target.starts_with("thumbv") { + if env::var_os("CARGO_FEATURE_INLINE_ASM").is_none() { + fs::copy( + format!("../bin/{}.a", target), + out_dir.join(format!("lib{}.a", name)), + ) + .unwrap(); + + println!("cargo:rustc-link-lib=static={}", name); + println!("cargo:rustc-link-search={}", out_dir.display()); + } + + println!("cargo:rustc-cfg=thumb"); + } +} diff --git a/cortex-m-semihosting/src/debug.rs b/cortex-m-semihosting/src/debug.rs new file mode 100644 index 0000000..a4fa6d8 --- /dev/null +++ b/cortex-m-semihosting/src/debug.rs @@ -0,0 +1,96 @@ +//! 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 cortex_m_semihosting::debug::{self, EXIT_SUCCESS, EXIT_FAILURE}; +//! +//! fn main() { +//! 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/cortex-m-semihosting/src/export.rs b/cortex-m-semihosting/src/export.rs new file mode 100644 index 0000000..c188ab0 --- /dev/null +++ b/cortex-m-semihosting/src/export.rs @@ -0,0 +1,51 @@ +//! IMPLEMENTATION DETAILS USED BY MACROS + +use core::fmt::{self, Write}; + +use cortex_m::interrupt; + +use crate::hio::{self, HStderr, HStdout}; + +static mut HSTDOUT: Option<HStdout> = None; + +pub fn hstdout_str(s: &str) { + let _result = interrupt::free(|_| 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 = interrupt::free(|_| unsafe { + if HSTDOUT.is_none() { + HSTDOUT = Some(hio::hstdout()?); + } + + HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + }); +} + +static mut HSTDERR: Option<HStderr> = None; + +pub fn hstderr_str(s: &str) { + let _result = interrupt::free(|_| 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 = interrupt::free(|_| unsafe { + if HSTDERR.is_none() { + HSTDERR = Some(hio::hstderr()?); + } + + HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + }); +} diff --git a/cortex-m-semihosting/src/hio.rs b/cortex-m-semihosting/src/hio.rs new file mode 100644 index 0000000..61ac749 --- /dev/null +++ b/cortex-m-semihosting/src/hio.rs @@ -0,0 +1,88 @@ +//! Host I/O + +use core::{fmt, slice}; +use crate::nr; + +/// Host's standard error +#[derive(Clone, Copy)] +pub struct HStderr { + fd: usize, +} + +impl HStderr { + /// 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 HStderr { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_all(s.as_bytes()).map_err(|_| fmt::Error) + } +} + +/// Host's standard output +#[derive(Clone, Copy)] +pub struct HStdout { + fd: usize, +} + +impl HStdout { + /// 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 HStdout { + 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<HStderr, ()> { + // 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).map(|fd| HStderr { fd }) +} + +/// Construct a new handle to the host's standard output. +pub fn hstdout() -> Result<HStdout, ()> { + open(":tt\0", nr::open::W_TRUNC).map(|fd| HStdout { fd }) +} + +fn open(name: &str, mode: usize) -> Result<usize, ()> { + let name = name.as_bytes(); + match unsafe { syscall!(OPEN, name.as_ptr(), mode, name.len() - 1) } as + isize { + -1 => Err(()), + fd => Ok(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/cortex-m-semihosting/src/lib.rs b/cortex-m-semihosting/src/lib.rs new file mode 100644 index 0000000..b70dea0 --- /dev/null +++ b/cortex-m-semihosting/src/lib.rs @@ -0,0 +1,232 @@ +//! Semihosting for ARM Cortex-M processors +//! +//! # What is semihosting? +//! +//! "Semihosting is a mechanism that enables code running on an ARM target to communicate and use +//! the Input/Output facilities on a host computer that is running a debugger." - ARM +//! +//! # 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 cortex_m_semihosting::hio; +//! use core::fmt::Write; +//! +//! // This function will be called by the application +//! fn print() -> Result<(), core::fmt::Error> { +//! let mut stdout = match hio::hstdout() { +//! Ok(fd) => fd, +//! Err(()) => return 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 cortex_m_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 +//! +//! ## `inline-asm` +//! +//! When this feature is enabled semihosting is implemented using inline assembly (`llvm_asm!`) and +//! compiling this crate requires nightly. +//! +//! When this feature is disabled semihosting is implemented using FFI calls into an external +//! assembly file and compiling this crate works on stable and beta. +//! +//! ## `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 to `bkpt` are patched out. +//! +//! # Reference +//! +//! For documentation about the semihosting operations, check: +//! +//! 'Chapter 8 - Semihosting' of the ['ARM Compiler toolchain Version 5.0'][pdf] +//! manual. +//! +//! [pdf]: http://infocenter.arm.com/help/topic/com.arm.doc.dui0471e/DUI0471E_developing_for_arm_processors.pdf + +#![cfg_attr(feature = "inline-asm", feature(llvm_asm))] +#![deny(missing_docs)] +#![no_std] + +#[macro_use] +mod macros; + +pub mod debug; +#[doc(hidden)] +pub mod export; +pub mod hio; +pub mod nr; + +#[cfg(all(thumb, not(feature = "inline-asm")))] +extern "C" { + fn __syscall(nr: usize, arg: usize) -> usize; +} + +/// Performs a semihosting operation, takes a pointer to an argument block +#[inline(always)] +pub unsafe fn syscall<T>(nr: usize, arg: &T) -> usize { + syscall1(nr, arg as *const T as usize) +} + +/// Performs a semihosting operation, takes one integer as an argument +#[inline(always)] +pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize { + match () { + #[cfg(all(thumb, not(feature = "inline-asm"), not(feature = "no-semihosting")))] + () => __syscall(_nr, _arg), + + #[cfg(all(thumb, feature = "inline-asm", not(feature = "no-semihosting")))] + () => { + let mut nr = _nr; + llvm_asm!("bkpt 0xAB" : "+{r0}"(nr) : "{r1}"(_arg) :: "volatile"); + nr + } + + #[cfg(all(thumb, feature = "no-semihosting"))] + () => { + 0 + } + + #[cfg(not(thumb))] + () => unimplemented!(), + } +} diff --git a/cortex-m-semihosting/src/macros.rs b/cortex-m-semihosting/src/macros.rs new file mode 100644 index 0000000..d10cd3f --- /dev/null +++ b/cortex-m-semihosting/src/macros.rs @@ -0,0 +1,119 @@ +/// 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/cortex-m-semihosting/src/nr.rs b/cortex-m-semihosting/src/nr.rs new file mode 100644 index 0000000..5d206de --- /dev/null +++ b/cortex-m-semihosting/src/nr.rs @@ -0,0 +1,57 @@ +//! Semihosting operations + +// TODO document +#![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; +} |