Skip to content

Commit 2e40ad4

Browse files
Merge pull request #945 from knurling-rs/semihosting-updates
Clean up defmt-semihosting.
2 parents 9fbd524 + 9f6aae5 commit 2e40ad4

File tree

3 files changed

+120
-46
lines changed

3 files changed

+120
-46
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ Initial release
710710
### [defmt-semihosting-next]
711711

712712
* [#943] use critical_section for synchronization.
713+
* [#945] switch to using semihosting crate and `UnsafeCell`
713714

714715
### [defmt-semihosting-v0.1.0] (2024-11-27)
715716

firmware/defmt-semihosting/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ repository = "https://github.com/knurling-rs/defmt"
1111
version = "0.1.0"
1212

1313
[dependencies]
14-
defmt = { version = "0.3", path = "../../defmt" }
1514
cortex-m = "0.7"
16-
cortex-m-semihosting = "0.5"
1715
critical-section = "1.2"
16+
defmt = { version = "0.3", path = "../../defmt" }
17+
semihosting = { version = "0.1.19", features = ["stdio"] }
1818

1919
[features]
2020
critical-section-single-core = ["cortex-m/critical-section-single-core"]

firmware/defmt-semihosting/src/lib.rs

+117-44
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,142 @@
11
//! `defmt` global logger over semihosting
22
//!
3-
//! NOTE this is meant to only be used with QEMU
4-
//!
5-
//! WARNING using `cortex_m_semihosting`'s `hprintln!` macro or `HStdout` API will corrupt `defmt`
6-
//! log frames so don't use those APIs.
3+
//! WARNING using `semihosting`'s `println!` macro or `Stdout` API will corrupt
4+
//! `defmt` log frames so don't use those APIs.
75
//!
86
//! # Critical section implementation
97
//!
10-
//! This crate uses [`critical-section`](https://github.com/rust-embedded/critical-section) to ensure only one thread
11-
//! is writing to the buffer at a time. You must import a crate that provides a `critical-section` implementation
12-
//! suitable for the current target. See the `critical-section` README for details.
8+
//! This crate uses
9+
//! [`critical-section`](https://github.com/rust-embedded/critical-section) to
10+
//! ensure only one thread is writing to the buffer at a time. You must import a
11+
//! crate that provides a `critical-section` implementation suitable for the
12+
//! current target. See the `critical-section` README for details.
1313
//!
14-
//! For example, for single-core privileged-mode Cortex-M targets, you can add the following to your Cargo.toml.
14+
//! For example, for single-core privileged-mode Cortex-M targets, you can add
15+
//! the following to your Cargo.toml.
1516
//!
1617
//! ```toml
1718
//! [dependencies]
1819
//! cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
1920
//! ```
2021
2122
#![no_std]
22-
// nightly warns about static_mut_refs, but 1.76 (our MSRV) does not know about
23-
// that lint yet, so we ignore it it but also ignore if it is unknown
24-
#![allow(unknown_lints, static_mut_refs)]
2523

26-
use core::sync::atomic::{AtomicBool, Ordering};
24+
use core::{
25+
cell::UnsafeCell,
26+
sync::atomic::{AtomicBool, Ordering},
27+
};
2728

28-
use cortex_m_semihosting::hio;
29+
use semihosting::io::{Stdout, Write as _};
2930

3031
#[defmt::global_logger]
3132
struct Logger;
3233

33-
static TAKEN: AtomicBool = AtomicBool::new(false);
34-
static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
35-
static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
34+
static SEMIHOSTING_ENCODER: SemihostingEncoder = SemihostingEncoder::new();
35+
36+
struct SemihostingEncoder {
37+
/// A boolean lock
38+
///
39+
/// Is `true` when `acquire` has been called and we have exclusive access to
40+
/// the rest of this structure.
41+
taken: AtomicBool,
42+
/// We need to remember this to exit a critical section
43+
cs_restore: UnsafeCell<critical_section::RestoreState>,
44+
/// A defmt::Encoder for encoding frames
45+
encoder: UnsafeCell<defmt::Encoder>,
46+
/// A semihosting handle for outputting encoded data
47+
handle: UnsafeCell<Option<Stdout>>,
48+
}
3649

37-
unsafe impl defmt::Logger for Logger {
38-
fn acquire() {
50+
impl SemihostingEncoder {
51+
/// Create a new semihosting-based defmt-encoder
52+
const fn new() -> SemihostingEncoder {
53+
SemihostingEncoder {
54+
taken: AtomicBool::new(false),
55+
cs_restore: UnsafeCell::new(critical_section::RestoreState::invalid()),
56+
encoder: UnsafeCell::new(defmt::Encoder::new()),
57+
handle: UnsafeCell::new(None),
58+
}
59+
}
60+
61+
/// Acquire the defmt encoder.
62+
fn acquire(&self) {
3963
// safety: Must be paired with corresponding call to release(), see below
4064
let restore = unsafe { critical_section::acquire() };
4165

42-
if TAKEN.load(Ordering::Relaxed) {
66+
// NB: You can re-enter critical sections but we need to make sure
67+
// no-one does that.
68+
if self.taken.load(Ordering::Relaxed) {
4369
panic!("defmt logger taken reentrantly")
4470
}
4571

46-
// no need for CAS because interrupts are disabled
47-
TAKEN.store(true, Ordering::Relaxed);
72+
// no need for CAS because we are in a critical section
73+
self.taken.store(true, Ordering::Relaxed);
74+
75+
// safety: accessing the cell is OK because we have acquired a critical
76+
// section.
77+
unsafe {
78+
self.cs_restore.get().write(restore);
79+
let encoder: &mut defmt::Encoder = &mut *self.encoder.get();
80+
let handle: &mut Option<Stdout> = &mut *self.handle.get();
81+
if handle.is_none() {
82+
*handle = semihosting::io::stdout().ok();
83+
}
84+
encoder.start_frame(|b| {
85+
if let Some(h) = handle {
86+
_ = h.write_all(b);
87+
}
88+
});
89+
}
90+
}
91+
92+
/// Release the defmt encoder.
93+
unsafe fn release(&self) {
94+
if !self.taken.load(Ordering::Relaxed) {
95+
panic!("defmt release out of context")
96+
}
97+
98+
// safety: accessing the cell is OK because we have acquired a critical
99+
// section.
100+
unsafe {
101+
let encoder: &mut defmt::Encoder = &mut *self.encoder.get();
102+
let handle: &mut Option<Stdout> = &mut *self.handle.get();
103+
encoder.end_frame(|b| {
104+
if let Some(h) = handle {
105+
_ = h.write_all(b);
106+
}
107+
});
108+
let restore = self.cs_restore.get().read();
109+
self.taken.store(false, Ordering::Relaxed);
110+
// paired with exactly one acquire call
111+
critical_section::release(restore);
112+
}
113+
}
114+
115+
/// Write bytes to the defmt encoder.
116+
unsafe fn write(&self, bytes: &[u8]) {
117+
if !self.taken.load(Ordering::Relaxed) {
118+
panic!("defmt write out of context")
119+
}
120+
121+
// safety: accessing the cell is OK because we have acquired a critical
122+
// section.
123+
unsafe {
124+
let encoder: &mut defmt::Encoder = &mut *self.encoder.get();
125+
let handle: &mut Option<Stdout> = &mut *self.handle.get();
126+
encoder.write(bytes, |b| {
127+
if let Some(h) = handle {
128+
_ = h.write_all(b);
129+
}
130+
});
131+
}
132+
}
133+
}
48134

49-
// safety: accessing the `static mut` is OK because we have acquired a critical section.
50-
unsafe { CS_RESTORE = restore };
135+
unsafe impl Sync for SemihostingEncoder {}
51136

52-
// safety: accessing the `static mut` is OK because we have disabled interrupts.
53-
unsafe { ENCODER.start_frame(do_write) }
137+
unsafe impl defmt::Logger for Logger {
138+
fn acquire() {
139+
SEMIHOSTING_ENCODER.acquire();
54140
}
55141

56142
unsafe fn flush() {
@@ -61,27 +147,14 @@ unsafe impl defmt::Logger for Logger {
61147
}
62148

63149
unsafe fn release() {
64-
// safety: accessing the `static mut` is OK because we have disabled interrupts.
65-
ENCODER.end_frame(do_write);
66-
67-
TAKEN.store(false, Ordering::Relaxed);
68-
69-
// safety: accessing the `static mut` is OK because we have acquired a critical section.
70-
let restore = unsafe { CS_RESTORE };
71-
72-
// safety: Must be paired with corresponding call to acquire(), see above
73-
unsafe { critical_section::release(restore) };
150+
unsafe {
151+
SEMIHOSTING_ENCODER.release();
152+
}
74153
}
75154

76155
unsafe fn write(bytes: &[u8]) {
77-
// safety: accessing the `static mut` is OK because we have disabled interrupts.
78-
ENCODER.write(bytes, do_write);
79-
}
80-
}
81-
82-
fn do_write(bytes: &[u8]) {
83-
// using QEMU; it shouldn't mind us opening several handles (I hope)
84-
if let Ok(mut hstdout) = hio::hstdout() {
85-
hstdout.write_all(bytes).ok();
156+
unsafe {
157+
SEMIHOSTING_ENCODER.write(bytes);
158+
}
86159
}
87160
}

0 commit comments

Comments
 (0)