diff --git a/Cargo.toml b/Cargo.toml index eddc6fab..e3c6a3f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ inline-asm = [] linker-plugin-lto = [] [workspace] -members = ["xtask"] +members = ["xtask", "cortex-m-semihosting", "panic-semihosting"] [package.metadata.docs.rs] targets = [ diff --git a/asm/inline.rs b/asm/inline.rs index 9a0c66e1..688604e5 100644 --- a/asm/inline.rs +++ b/asm/inline.rs @@ -175,6 +175,13 @@ pub unsafe fn __wfi() { asm!("wfi"); } +/// Semihosting syscall. +#[inline(always)] +pub unsafe fn __syscall(mut nr: u32, arg: u32) -> u32 { + asm!("bkpt #0xab", inout("r0") nr, in("r1") arg); + nr +} + // v7m *AND* v8m.main, but *NOT* v8m.base #[cfg(any(armv7m, armv8m_main))] pub use self::v7m::*; diff --git a/asm/lib.rs b/asm/lib.rs index 93d56fba..ec46d5be 100644 --- a/asm/lib.rs +++ b/asm/lib.rs @@ -46,7 +46,7 @@ macro_rules! shims { pub unsafe extern "C" fn $name( $($arg: $argty),* ) $(-> $ret)? { - crate::inline::$name($($arg)*) + crate::inline::$name($($arg),*) } )+ }; @@ -72,9 +72,10 @@ shims! { fn __udf(); fn __wfe(); fn __wfi(); + fn __syscall(nr: u32, arg: u32) -> u32; } -// v7m *AND* v8m.main, but *NOT* v8m.base +// v7m *AND* v8m.main, but *NOT* v8m.base #[cfg(any(armv7m, armv8m_main))] shims! { fn __basepri_max(val: u8); diff --git a/bin/thumbv6m-none-eabi-lto.a b/bin/thumbv6m-none-eabi-lto.a index 6ee04278..93d29537 100644 Binary files a/bin/thumbv6m-none-eabi-lto.a and b/bin/thumbv6m-none-eabi-lto.a differ diff --git a/bin/thumbv6m-none-eabi.a b/bin/thumbv6m-none-eabi.a index 34dba4a6..5fbc73cc 100644 Binary files a/bin/thumbv6m-none-eabi.a and b/bin/thumbv6m-none-eabi.a differ diff --git a/bin/thumbv7em-none-eabi-lto.a b/bin/thumbv7em-none-eabi-lto.a index 781ea4c0..608cbf1e 100644 Binary files a/bin/thumbv7em-none-eabi-lto.a and b/bin/thumbv7em-none-eabi-lto.a differ diff --git a/bin/thumbv7em-none-eabi.a b/bin/thumbv7em-none-eabi.a index 05d98bad..665ff58c 100644 Binary files a/bin/thumbv7em-none-eabi.a and b/bin/thumbv7em-none-eabi.a differ diff --git a/bin/thumbv7em-none-eabihf-lto.a b/bin/thumbv7em-none-eabihf-lto.a index b0a22044..feecade9 100644 Binary files a/bin/thumbv7em-none-eabihf-lto.a and b/bin/thumbv7em-none-eabihf-lto.a differ diff --git a/bin/thumbv7em-none-eabihf.a b/bin/thumbv7em-none-eabihf.a index bd416b5a..75177201 100644 Binary files a/bin/thumbv7em-none-eabihf.a and b/bin/thumbv7em-none-eabihf.a differ diff --git a/bin/thumbv7m-none-eabi-lto.a b/bin/thumbv7m-none-eabi-lto.a index a6ff71af..bf842386 100644 Binary files a/bin/thumbv7m-none-eabi-lto.a and b/bin/thumbv7m-none-eabi-lto.a differ diff --git a/bin/thumbv7m-none-eabi.a b/bin/thumbv7m-none-eabi.a index 1f57c90b..803a0ba9 100644 Binary files a/bin/thumbv7m-none-eabi.a and b/bin/thumbv7m-none-eabi.a differ diff --git a/bin/thumbv8m.base-none-eabi-lto.a b/bin/thumbv8m.base-none-eabi-lto.a index d157ab53..559bf13e 100644 Binary files a/bin/thumbv8m.base-none-eabi-lto.a and b/bin/thumbv8m.base-none-eabi-lto.a differ diff --git a/bin/thumbv8m.base-none-eabi.a b/bin/thumbv8m.base-none-eabi.a index 6142436a..abfa14f1 100644 Binary files a/bin/thumbv8m.base-none-eabi.a and b/bin/thumbv8m.base-none-eabi.a differ diff --git a/bin/thumbv8m.main-none-eabi-lto.a b/bin/thumbv8m.main-none-eabi-lto.a index f5849b1e..1a381f83 100644 Binary files a/bin/thumbv8m.main-none-eabi-lto.a and b/bin/thumbv8m.main-none-eabi-lto.a differ diff --git a/bin/thumbv8m.main-none-eabi.a b/bin/thumbv8m.main-none-eabi.a index 63a0dd02..4d4d5c6d 100644 Binary files a/bin/thumbv8m.main-none-eabi.a and b/bin/thumbv8m.main-none-eabi.a differ diff --git a/bin/thumbv8m.main-none-eabihf-lto.a b/bin/thumbv8m.main-none-eabihf-lto.a index 5dae3aa3..ab826096 100644 Binary files a/bin/thumbv8m.main-none-eabihf-lto.a and b/bin/thumbv8m.main-none-eabihf-lto.a differ diff --git a/bin/thumbv8m.main-none-eabihf.a b/bin/thumbv8m.main-none-eabihf.a index 3989cda9..2d4703a1 100644 Binary files a/bin/thumbv8m.main-none-eabihf.a and b/bin/thumbv8m.main-none-eabihf.a differ diff --git a/cortex-m-semihosting/CHANGELOG.md b/cortex-m-semihosting/CHANGELOG.md new file mode 100644 index 00000000..f813f8ba --- /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 00000000..8eb28b07 --- /dev/null +++ b/cortex-m-semihosting/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = [ + "The Cortex-M Team ", + "Jorge Aparicio ", +] +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 00000000..bfbfb44a --- /dev/null +++ b/cortex-m-semihosting/README.md @@ -0,0 +1,40 @@ +[![crates.io](https://img.shields.io/crates/v/cortex-m-semihosting.svg)](https://crates.io/crates/cortex-m-semihosting) +[![crates.io](https://img.shields.io/crates/d/cortex-m-semihosting.svg)](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 00000000..5fc6a049 --- /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 00000000..a4fa6d83 --- /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 00000000..c188ab08 --- /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 = 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 = 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 00000000..61ac749e --- /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 { + // 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 { + open(":tt\0", nr::open::W_TRUNC).map(|fd| HStdout { fd }) +} + +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(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 00000000..b70dea09 --- /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(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 00000000..d10cd3f4 --- /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 00000000..5d206dec --- /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; +} diff --git a/panic-semihosting/CHANGELOG.md b/panic-semihosting/CHANGELOG.md new file mode 100644 index 00000000..0036754c --- /dev/null +++ b/panic-semihosting/CHANGELOG.md @@ -0,0 +1,66 @@ +# 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] + +## [v0.5.3] - 2019-09-01 + +- Added feature `jlink-quirks` to work with JLink + +## [v0.5.2] - 2019-04-28 + +- Updated `cortex-m` version to not have the issue when linking multiple + versions of it. + +## [v0.5.1] - 2018-10-27 + +### Added + +- An opt-in "exit" Cargo feature to have the panic handler perform an exit + semihosting call after logging the panic message. + +## [v0.5.0] - 2018-09-10 + +- [breaking-change] The `panic_handler` feature gate has been removed. This + crate will compile on 1.30-beta and on stable 1.30 when they are released. + +## [v0.4.0] - 2018-09-03 + +### Changed + +- This crate no longer depends on `arm-none-eabi-gcc`. + +- [breaking-change] Move from the `panic_implementation` attribute to the + `panic_handler` attribute, which will be stabilized. + +## [v0.3.0] - 2018-06-04 + +### Changed + +- [breaking-change] moved from the, now removed, `panic_fmt` lang item to the + `#[panic_implementation]` attribute. + +## [v0.2.0] - 2018-05-11 + +### Changed + +- [breaking-change] made inline assembly (`asm!`) opt-in via the `"inline-asm"` feature. This is a + breaking change because this crate now requires `arm-none-eabi-gcc` to be installed to build + without the `"inline-asm"` feature, which is the default. + +## v0.1.0 - 2018-04-09 + +Initial release + +[Unreleased]: https://github.com/rust-embedded/panic-semihosting/compare/v0.5.3...HEAD +[v0.5.3]: https://github.com/rust-embedded/panic-semihosting/compare/v0.5.2...v0.5.3 +[v0.5.2]: https://github.com/rust-embedded/panic-semihosting/compare/v0.5.1...v0.5.2 +[v0.5.1]: https://github.com/rust-embedded/panic-semihosting/compare/v0.5.0...v0.5.1 +[v0.5.0]: https://github.com/rust-embedded/panic-semihosting/compare/v0.4.0...v0.5.0 +[v0.4.0]: https://github.com/rust-embedded/panic-semihosting/compare/v0.3.0...v0.4.0 +[v0.3.0]: https://github.com/rust-embedded/panic-semihosting/compare/v0.2.0...v0.3.0 +[v0.2.0]: https://github.com/rust-embedded/panic-semihosting/compare/v0.1.0...v0.2.0 diff --git a/panic-semihosting/Cargo.toml b/panic-semihosting/Cargo.toml new file mode 100644 index 00000000..2e6c3bef --- /dev/null +++ b/panic-semihosting/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = [ + "The Cortex-M Team ", + "Jorge Aparicio ", +] +categories = ["no-std"] +description = "Report panic messages to the host stderr using semihosting" +documentation = "https://docs.rs/panic-semihosting" +keywords = ["panic-handler", "panic-impl", "panic", "semihosting"] +license = "MIT OR Apache-2.0" +name = "panic-semihosting" +repository = "https://github.com/rust-embedded/cortex-m" +version = "0.5.3" + +[dependencies] +cortex-m = { path = "..", version = ">= 0.5.6, < 0.7" } +cortex-m-semihosting = { path = "../cortex-m-semihosting", version = "0.3" } + +[features] +exit = [] +inline-asm = ["cortex-m-semihosting/inline-asm", "cortex-m/inline-asm"] +jlink-quirks = ["cortex-m-semihosting/jlink-quirks"] diff --git a/panic-semihosting/README.md b/panic-semihosting/README.md new file mode 100644 index 00000000..baacf1af --- /dev/null +++ b/panic-semihosting/README.md @@ -0,0 +1,37 @@ +# `panic-semihosting` + +> Report panic messages to the host stderr using semihosting + +This project is developed and maintained by the [Cortex-M team][team]. + +## [Documentation](https://docs.rs/panic-semihosting) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.32.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/panic-semihosting/src/lib.rs b/panic-semihosting/src/lib.rs new file mode 100644 index 00000000..1db7b72d --- /dev/null +++ b/panic-semihosting/src/lib.rs @@ -0,0 +1,96 @@ +//! Report panic messages to the host stderr using semihosting +//! +//! This crate contains an implementation of `panic_fmt` that logs panic messages to the host stderr +//! using [`cortex-m-semihosting`]. Before logging the message the panic handler disables (masks) +//! the device specific interrupts. After logging the message the panic handler trigger a breakpoint +//! and then goes into an infinite loop. +//! +//! Currently, this crate only supports the ARM Cortex-M architecture. +//! +//! [`cortex-m-semihosting`]: https://crates.io/crates/cortex-m-semihosting +//! +//! # Usage +//! +//! ``` ignore +//! #![no_std] +//! +//! extern crate panic_semihosting; +//! +//! fn main() { +//! panic!("FOO") +//! } +//! ``` +//! +//! ``` text +//! (gdb) monitor arm semihosting enable +//! (gdb) continue +//! Program received signal SIGTRAP, Trace/breakpoint trap. +//! rust_begin_unwind (args=..., file=..., line=8, col=5) +//! at $CRATE/src/lib.rs:69 +//! 69 asm::bkpt(); +//! ``` +//! +//! ``` text +//! $ openocd -f (..) +//! (..) +//! panicked at 'FOO', src/main.rs:6:5 +//! ``` +//! +//! # Optional features +//! +//! ## `exit` +//! +//! When this feature is enabled the panic handler performs an exit semihosting call after logging +//! the panic message. This is useful when emulating the program on QEMU as it causes the QEMU +//! process to exit with a non-zero exit code; thus it can be used to implement Cortex-M tests that +//! run on the host. +//! +//! We discourage using this feature when the program will run on hardware as the exit call can +//! leave the hardware debugger in an inconsistent state. +//! +//! ## `inline-asm` +//! +//! When this feature is enabled semihosting is implemented using inline assembly (`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. + +#![cfg(all(target_arch = "arm", target_os = "none"))] +#![deny(missing_docs)] +#![deny(warnings)] +#![no_std] + +extern crate cortex_m; +extern crate cortex_m_semihosting as sh; + +use core::fmt::Write; +use core::panic::PanicInfo; + +#[cfg(not(feature = "exit"))] +use cortex_m::asm; +use cortex_m::interrupt; +#[cfg(feature = "exit")] +use sh::debug::{self, EXIT_FAILURE}; +use sh::hio; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + interrupt::disable(); + + if let Ok(mut hstdout) = hio::hstdout() { + writeln!(hstdout, "{}", info).ok(); + } + + match () { + // Exit the QEMU process + #[cfg(feature = "exit")] + () => debug::exit(EXIT_FAILURE), + // OK to fire a breakpoint here because we know the microcontroller is connected to a + // debugger + #[cfg(not(feature = "exit"))] + () => asm::bkpt(), + } + + loop {} +} diff --git a/xtask/tests/ci.rs b/xtask/tests/ci.rs index 48356e49..5b449ba2 100644 --- a/xtask/tests/ci.rs +++ b/xtask/tests/ci.rs @@ -2,6 +2,9 @@ use std::process::Command; use std::{env, str}; use xtask::{check_blobs, install_targets}; +/// List of all compilation targets we support. +/// +/// This should generally list all of the bare-metal thumb targets starting at thumbv6. static TARGETS: &[&str] = &[ "thumbv6m-none-eabi", "thumbv7m-none-eabi", @@ -12,16 +15,71 @@ static TARGETS: &[&str] = &[ "thumbv8m.main-none-eabihf", ]; -fn build(target: &str, features: &[&str]) { - println!("building for {} {:?}", target, features); +fn build(package: &str, target: &str, features: &[&str]) { + println!("building {} for {} {:?}", package, target, features); let mut cargo = Command::new("cargo"); - cargo.args(&["build", "--target", target]); + cargo.args(&["build", "-p", package, "--target", target]); for feat in features { cargo.args(&["--features", *feat]); } + // Cargo features don't work right when invoked from the workspace root, so change to the + // package's directory when necessary. + if package != "cortex-m" { + cargo.current_dir(package); + } + let status = cargo.status().unwrap(); - assert!(status.success()); + assert!(status.success(), "failed to execute: {:?}", cargo); +} + +#[rustfmt::skip] +static PACKAGE_FEATURES: &[(&str, &[&str])] = &[ + ("cortex-m", &["inline-asm", "cm7-r0p1"]), // no `linker-plugin-lto` since it's experimental + ("cortex-m-semihosting", &["inline-asm", "no-semihosting", "jlink-quirks"]), + ("panic-semihosting", &["inline-asm", "exit", "jlink-quirks"]), +]; + +fn check_crates_build(is_nightly: bool) { + // Build all crates for each supported target. + for &target in TARGETS { + // Filters crate features, keeping only those that are supported. + // Relies on all crates in this repo to use the same convention. + let should_use_feature = |feat: &str| { + match feat { + // This is nightly-only, so don't use it on stable. + "inline-asm" => is_nightly, + // This only affects thumbv7em targets. + "cm7-r0p1" => target.starts_with("thumbv7em"), + + _ => true, + } + }; + + for (package, all_features) in PACKAGE_FEATURES { + // Every crate must build with the default feature set. + build(package, target, &[]); + + let used_features = &*all_features + .iter() + .copied() + .filter(|feat| should_use_feature(*feat)) + .collect::>(); + + // (note: we don't test with default features disabled, since we don't use them yet) + + // Every crate must build with each individual feature enabled. + for feat in used_features { + build(package, target, &[*feat]); + } + + // Every crate must build with *all* features enabled. + build(package, target, used_features); + + // (technically we should be checking the powerset of all features if we wanted to be + // *really* sure, but that takes too much time and isn't very easy to implement) + } + } } fn main() { @@ -36,22 +94,5 @@ fn main() { let output = Command::new("rustc").arg("-V").output().unwrap(); let is_nightly = str::from_utf8(&output.stdout).unwrap().contains("nightly"); - // Build `cortex-m` for each supported target. - for target in TARGETS { - build(*target, &[]); - - if is_nightly { - // This may fail when nightly breaks. That's fine, the CI job isn't essential. - build(*target, &["inline-asm"]); - } - - if target.starts_with("thumbv7em") { - // These can target Cortex-M7s, which have an errata workaround. - build(*target, &["cm7-r0p1"]); - - if is_nightly { - build(*target, &["inline-asm", "cm7-r0p1"]); - } - } - } + check_crates_build(is_nightly); }