Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use critical-section 1.0 for locking. #28

Merged
merged 1 commit into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 3 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,13 @@ Target side implementation of the RTT (Real-Time Transfer) I/O protocol. RTT imp

## Platform support

While this crate is platform agnostic, some platform-specific code is needed for locking if you want to use the global `rprintln!` macro.
To use the global `rprintln!` macro, a platform-specific [`critical-section`](https://github.com/rust-embedded/critical-section) implementation is needed for locking.

If using Cortex-M or RISC-V, there is built-in support with a feature flag:

```toml
# Cargo.toml
rtt-target = { version = "x.y.z", features = ["cortex-m"] }
# or
rtt-target = { version = "x.y.z", features = ["riscv"] }
```

Otherwise, check the documentation for the `set_print_channel_cs` function.

Output directly to a channel object with `write!` or the binary `write` method does not require locking and therefore does not need any platform-specific code.
Output directly to a channel object with `write!` or the binary `write` method does not require locking and therefore does not need any platform-specific critical section.

## Usage

With a platform support feature, printing is as simple as:
With a platform-specific critical section in use, printing is as simple as:

```rust
use rtt_target::{rtt_init_print, rprintln};
Expand Down
6 changes: 3 additions & 3 deletions examples-cortex-m/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ authors = ["Matti Virkkunen <mvirkkunen@gmail.com>"]
edition = "2018"

[dependencies]
cortex-m = "0.7.1"
cortex-m-rt = "0.6.12"
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"
panic-halt = "0.2.0"
rtt-target = { path = "../rtt-target", features = ["cortex-m"] }
rtt-target = { path = "../rtt-target" }
ufmt = "0.1.0"

[[bin]]
Expand Down
2 changes: 1 addition & 1 deletion panic-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ authors = ["Matti Virkkunen <mvirkkunen@gmail.com>"]
edition = "2018"

[dependencies]
cortex-m-rt = "0.6.12"
cortex-m-rt = "0.7"
panic-rtt-target = { path = "../panic-rtt-target", features = ["cortex-m"] }
rtt-target = { path = "../rtt-target" }
7 changes: 1 addition & 6 deletions rtt-target/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,5 @@ repository = "https://github.com/mvirkkunen/rtt-target"

[dependencies]
ufmt-write = "0.1.0"
critical-section = "1.0.0"

# Platform specific stuff
cortex-m = { version = "0.7.1", optional = true }
riscv = { version = "0.6.0", optional = true }

[package.metadata.docs.rs]
features = ["cortex-m"]
120 changes: 11 additions & 109 deletions rtt-target/src/print.rs
Original file line number Diff line number Diff line change
@@ -1,80 +1,16 @@
use crate::{TerminalChannel, TerminalWriter, UpChannel};
use core::cell::RefCell;
use core::fmt::{self, Write as _};
use core::mem::MaybeUninit;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};

static CRITICAL_SECTION: AtomicPtr<CriticalSectionFunc> = AtomicPtr::new(core::ptr::null_mut());
static mut PRINT_TERMINAL: MaybeUninit<TerminalChannel> = MaybeUninit::uninit();

/// Type-erased critical section function used to synchronize printing.
///
/// When called, the function must establish a critical section and call `f` within it, passing
/// `arg` as the argument.
pub type CriticalSectionFunc = fn(arg: *mut (), f: fn(arg: *mut ()) -> ()) -> ();
use critical_section::Mutex;

/// Sets the channel to use for [`rprint`] and [`rprintln`] and the critical section function used
/// to synchronize printing. You should only use this function if the [`set_print_channel`] function
/// isn't available on your platform.
///
/// # Example
///
/// Because the function takes a *static reference to a function pointer* as an argument, the call
/// requires a manual cast. Luckily Rust will automatically promote a reference to a suitable
/// closure to `'static`. In this example, `interrupt::free` is a function that establishes a
/// critical section and calls the supplied function.
///
/// ```
/// use rtt_target::{rtt_init_detault, rprintln};
/// use platform_specific::interrupt;
///
/// fn main() -> ! {
/// let channels = rtt_init_detault!();
///
/// unsafe {
/// rtt_target::set_print_channel_cs(
/// channels.up.0,
/// &((|arg, f| interrupt::free(|_| f(arg))) as rtt_target::CriticalSectionFunc),
/// );
/// }
///
/// loop {
/// rprintln!("Hello, world!");
/// }
/// }
/// ```
///
/// # Safety
///
/// This function is unsafe because the user must guarantee that the `cs` function pointer passed in
/// adheres to the [`CriticalSectionFunc`] specification.
pub unsafe fn set_print_channel_cs(channel: UpChannel, cs: &'static CriticalSectionFunc) {
cs(channel.0 as *mut (), |channel_ptr| {
ptr::write(
PRINT_TERMINAL.as_mut_ptr(),
TerminalChannel::new(UpChannel(channel_ptr as *mut crate::rtt::RttChannel)),
);
});
use crate::{TerminalChannel, TerminalWriter, UpChannel};

CRITICAL_SECTION.store(cs as *const _ as *mut _, Ordering::SeqCst);
}
static PRINT_TERMINAL: Mutex<RefCell<Option<TerminalChannel>>> = Mutex::new(RefCell::new(None));

/// Sets the channel to use for [`rprint`] and [`rprintln`].
///
/// This function is available only if you have enabled a platform support feature. Otherwise,
/// [`set_print_channel_cs`] must be used.
#[cfg(any(feature = "cortex-m", feature = "riscv"))]
pub fn set_print_channel(channel: UpChannel) {
#[cfg(feature = "cortex-m")]
use cortex_m as arch;
#[cfg(feature = "riscv")]
use riscv as arch;
unsafe {
set_print_channel_cs(
channel,
&((|arg, f| arch::interrupt::free(|_| f(arg))) as CriticalSectionFunc),
);
}
critical_section::with(|cs| {
*PRINT_TERMINAL.borrow_ref_mut(cs) = Some(TerminalChannel::new(UpChannel(channel.0)))
});
}

/// Public due to access from macro.
Expand All @@ -83,22 +19,11 @@ pub mod print_impl {
use super::*;

fn with_writer<F: Fn(TerminalWriter) -> ()>(number: u8, f: F) {
let cs = CRITICAL_SECTION.load(Ordering::SeqCst);

if !cs.is_null() {
// If the critical section pointer has been set, PRINT_TERMINAL must also have been set.

let args = (number, f);

unsafe {
(&*cs)(&args as *const _ as *mut (), |args_ptr| {
let args = &*(args_ptr as *const (u8, F));
let term = &mut *PRINT_TERMINAL.as_mut_ptr();

(args.1)(term.write(args.0));
});
critical_section::with(|cs| {
if let Some(term) = &mut *PRINT_TERMINAL.borrow_ref_mut(cs) {
f(term.write(number))
}
}
});
}

/// Public due to access from macro.
Expand Down Expand Up @@ -177,10 +102,6 @@ macro_rules! rprintln {
///
/// The optional arguments specify the blocking mode (default: `NoBlockSkip`) and size of the buffer
/// in bytes (default: 1024). See [`rtt_init`] for more details.
///
/// This macro is defined only if the [`set_print_channel`] function is available, i.e. if you have
/// enabled a platform support feature.
#[cfg(any(feature = "cortex-m", feature = "riscv"))]
#[macro_export]
macro_rules! rtt_init_print {
($mode:ident, $size:literal) => {
Expand All @@ -205,22 +126,3 @@ macro_rules! rtt_init_print {
$crate::rtt_init_print!(NoBlockSkip, 1024);
};
}

/// This version of the macro only is defined if no platform support feature is enabled and outputs
/// a more friendly error message.
#[cfg(not(any(feature = "cortex-m", feature = "riscv")))]
#[macro_export]
macro_rules! rtt_init_print {
($($_:tt)*) => {
compile_error!(concat!(
"rtt_init_print! is only available if a platform support feature is enabled.\r\n",
"Solutions:\r\n",
"- Enable a platform support feature:\r\n",
" # Cargo.toml\r\n",
" rtt-target = { version = \"x.y.z\", features = [\"cortex-m\"] }\r\n",
" # or",
" rtt-target = { version = \"x.y.z\", features = [\"riscv\"] }\r\n",
"- OR use set_print_channel_cs() instead if you want to provide your own locking.\r\n"
))
};
}