riscv_semihosting/lib.rs
1//! Semihosting for RISCV processors
2//!
3//! # What is semihosting?
4//!
5//! "Semihosting is a technique where an application running in a debug or
6//! simulation environment can access elements of the system hosting the
7//! debugger or simulator including console, file system, time and other
8//! functions. This allows for diagnostics, interaction and measurement of a
9//! target system without requiring significant infrastructure to exist in that
10//! target environment." - RISC-V Semihosting Spec
11//!
12//! # Interface
13//!
14//! This crate provides implementations of
15//! [`core::fmt::Write`](https://doc.rust-lang.org/core/fmt/trait.Write.html),
16//! so you can use it, in conjunction with
17//! [`core::format_args!`](https://doc.rust-lang.org/core/macro.format_args.html)
18//! or the [`write!` macro](https://doc.rust-lang.org/core/macro.write.html),
19//! for user-friendly construction and printing of formatted strings.
20//!
21//! Since semihosting operations are modeled as [system calls][sc], this crate
22//! exposes an untyped `syscall!` interface just like the [`sc`] crate does.
23//!
24//! [sc]: https://en.wikipedia.org/wiki/System_call
25//! [`sc`]: https://crates.io/crates/sc
26//!
27//! # Forewarning
28//!
29//! Semihosting operations are *very* slow. Like, each WRITE operation can take
30//! hundreds of milliseconds.
31//!
32//! # Example
33//!
34//! ## Using `hio::hstdout`
35//!
36//! This example will demonstrate how to print formatted strings.
37//!
38//! ```no_run
39//! use riscv_semihosting::hio;
40//! use core::fmt::Write;
41//!
42//! // This function will be called by the application
43//! fn print() -> Result<(), core::fmt::Error> {
44//! let mut stdout = hio::hstdout().map_err(|_| core::fmt::Error)?;
45//! let language = "Rust";
46//! let ranking = 1;
47//!
48//! write!(stdout, "{} on embedded is #{}!", language, ranking)?;
49//!
50//! Ok(())
51//! }
52//! ```
53//!
54//! On the host side:
55//!
56//! ``` text
57//! $ openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log
58//! Open On-Chip Debugger 0.9.0 (2016-04-27-23:18)
59//! Licensed under GNU GPL v2
60//! For bug reports, read
61//! http://openocd.org/doc/doxygen/bugs.html
62//! # the command will block at this point
63//! ```
64//!
65//! The OpenOCD logs will be redirected to `/tmp/openocd.log`. You can view
66//! those logs in "real time" using `tail`
67//!
68//! ``` text
69//! $ tail -f /tmp/openocd.log
70//! Info : Unable to match requested speed 1000 kHz, using 950 kHz
71//! Info : Unable to match requested speed 1000 kHz, using 950 kHz
72//! Info : clock speed 950 kHz
73//! Info : STLINK v1 JTAG v11 API v2 SWIM v0 VID 0x0483 PID 0x3744
74//! Info : using stlink api v2
75//! Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints
76//! ```
77//!
78//! Alternatively you could omit the `-l` flag from the `openocd` call, and the
79//! `tail -f` command but the OpenOCD output will have intermingled in it logs
80//! from its normal operation.
81//!
82//! Then, we run the program:
83//!
84//! ``` text
85//! $ arm-none-eabi-gdb hello-world
86//! (gdb) # Connect to OpenOCD
87//! (gdb) target remote :3333
88//!
89//! (gdb) # Enable OpenOCD's semihosting support
90//! (gdb) monitor arm semihosting enable
91//!
92//! (gdb) # Flash the program
93//! (gdb) load
94//!
95//! (gdb) # Run the program
96//! (gdb) continue
97//! ```
98//!
99//! And you'll see the output under OpenOCD's terminal
100//!
101//! ``` text
102//! # openocd -f $INTERFACE -f $TARGET -l /tmp/openocd.log
103//! (..)
104//! Rust on embedded is #1!
105//! ```
106//! ## Using the syscall interface
107//!
108//! This example will show how to print "Hello, world!" on the host.
109//!
110//! Target program:
111//!
112//! ```no_run
113//! use riscv_semihosting::syscall;
114//!
115//! // This function will be called by the application
116//! fn print() {
117//! // File descriptor (on the host)
118//! const STDOUT: usize = 1; // NOTE the host stdout may not always be fd 1
119//! static MSG: &'static [u8] = b"Hello, world!\n";
120//!
121//! // Signature: fn write(fd: usize, ptr: *const u8, len: usize) -> usize
122//! let r = unsafe { syscall!(WRITE, STDOUT, MSG.as_ptr(), MSG.len()) };
123//! }
124//! ```
125//! Output and monitoring proceed as in the above example.
126//!
127//! ## The `dbg!` macro
128//!
129//! Analogous to [`std::dbg`](https://doc.rust-lang.org/std/macro.dbg.html) the
130//! macro `dbg!` returns a given expression and prints it using `heprintln!`
131//! including context for quick and dirty debugging.
132//!
133//! Panics if `heprintln!` returns an error.
134//!
135//! Example:
136//!
137//! ```no_run
138//! const UUID: *mut u32 = 0x0009_FC70 as *mut u32;
139//! dbg!(UUID);
140//! let mut uuid: [u32; 4] = [0; 4];
141//! for i in 0..4 {
142//! dbg!(i);
143//! uuid[i] = unsafe { dbg!(UUID.offset(i as isize).read_volatile()) };
144//! }
145//! ```
146//! outputs
147//! ```text
148//! [examples/semihosting.rs:37] UUID = 0x0009fc70
149//! [examples/semihosting.rs:40] i = 0
150//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 3370045464
151//! [examples/semihosting.rs:40] i = 1
152//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1426218275
153//! [examples/semihosting.rs:40] i = 2
154//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 2422621116
155//! [examples/semihosting.rs:40] i = 3
156//! [examples/semihosting.rs:41] UUID.offset(i as isize).read_volatile() = 1044138593
157//! ```
158//!
159//! # Optional features
160//!
161//! ## `jlink-quirks`
162//!
163//! When this feature is enabled, return values above `0xfffffff0` from
164//! semihosting operation `SYS_WRITE` (0x05) are interpreted as if the entire
165//! buffer had been written. The current latest version 6.48b of J-Link exhibits
166//! such behaviour, causing a panic if this feature is not enabled.
167//!
168//! ## `no-semihosting`
169//!
170//! When this feature is enabled, the underlying system calls are patched out.
171//!
172//! # Reference
173//!
174//! For documentation about the semihosting operations, check
175//! ['Semihosting for AArch32 and AArch64'](https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst).
176//! The RISC-V Semihosting spec is identical to Arm's with the exception of the
177//! assembly sequence necessary to trigger a semihosting call, so their
178//! documentation is sufficient.
179
180#![deny(missing_docs)]
181#![no_std]
182
183#[macro_use]
184mod macros;
185
186pub mod debug;
187#[doc(hidden)]
188pub mod export;
189pub mod hio;
190pub mod nr;
191
192/// Performs a semihosting operation, takes a pointer to an argument block
193///
194/// # Safety
195///
196/// The syscall number must be a valid [semihosting operation],
197/// and the arguments must be valid for the associated operation.
198///
199/// [semihosting operation]: https://developer.arm.com/documentation/dui0471/i/semihosting/semihosting-operations?lang=en
200#[inline(always)]
201pub unsafe fn syscall<T>(nr: usize, arg: &T) -> usize {
202 syscall1(nr, arg as *const T as usize)
203}
204
205/// Performs a semihosting operation, takes one integer as an argument
206///
207/// # Safety
208///
209/// Same as [`syscall()`].
210#[inline(always)]
211pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize {
212 match () {
213 #[cfg(all(
214 any(target_arch = "riscv32", target_arch = "riscv64"),
215 not(feature = "no-semihosting")
216 ))]
217 () => {
218 let mut nr = _nr;
219 let arg = _arg;
220 // The instructions below must always be uncompressed, otherwise
221 // it will be treated as a regular break, hence the norvc option.
222 //
223 // See https://github.com/riscv/riscv-semihosting-spec for more details.
224 core::arch::asm!("
225 .balign 16
226 .option push
227 .option norvc
228 slli x0, x0, 0x1f
229 ebreak
230 srai x0, x0, 0x7
231 .option pop
232 ",
233 inout("a0") nr,
234 inout("a1") arg => _,
235 options(nostack, preserves_flags),
236 );
237 nr
238 }
239 #[cfg(all(
240 any(target_arch = "riscv32", target_arch = "riscv64"),
241 feature = "no-semihosting"
242 ))]
243 () => 0,
244 #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))]
245 () => unimplemented!(),
246 }
247}