Skip to main content

arm_dcc/
lib.rs

1//! # Debug Communication Channel (DCC) API
2//!
3//! The Debug Communications Channel is a mechanism to get data from a *target*
4//! and into a *host*, and vice-versa. It works over a JTAG interface and so
5//! does not require a UART, or any dedicated I/O pins, and it does not stop the
6//! CPU whilst being used (unlike [semihosting]).
7//!
8//! DCC was added to the Arm Architecture Reference Manual in [ARMv7][armv7].
9//! Before that it was defined separately, usually as part of the Debug hardware
10//! in a specific ARM processor's Technical Reference Manual (like for the
11//! [ARM7TDMI][arm7tdmi]).
12//!
13//! This crate supports:
14//!
15//! * AArch64
16//! * ARMv7 AArch32
17//! * legacy ARM AArch32
18//!
19//! [semihosting]: https://crates.io/crates/semihosting
20//! [armv7]:
21//!     https://developer.arm.com/documentation/ddi0406/c/Debug-Architecture/The-Debug-Registers/Register-descriptions--in-register-order/DBGDSCR--Debug-Status-and-Control-Register?lang=en
22//! [arm7tdmi]:
23//!     https://developer.arm.com/documentation/ddi0210/c/Debug-Interface/Debug-Communications-Channel?lang=en
24//!
25//! # Example
26//!
27//! ## Device side
28//!
29//! ``` no_run
30//! use arm_dcc::dprintln;
31//!
32//! fn main() {
33//!     dprintln!("Hello, world!");
34//! }
35//! ```
36//!
37//! ## Host side
38//!
39//! ### Xilinx System Debugger
40//!
41//! ```text
42//! $ xsdb
43//!
44//! xsdb% # connect
45//! xsdb% conn
46//!
47//! xsdb% # select a Cortex-R core
48//! xsdb% targets -set 0
49//!
50//! xsdb% # hold the processor in reset state
51//! xsdb% rst -processor
52//!
53//! xsdb% # load program
54//! xsdb% dow hello.elf
55//!
56//! xsdb% # open a file
57//! xsdb% set f [open dcc.log w]
58//!
59//! xsdb% # redirect DCC output to file handle `f`
60//! xsdb% readjtaguart -start -handle $f
61//!
62//! xsdb% # start program execution
63//! xsdb% con
64//! ```
65//!
66//! ``` text
67//! $ # on another terminal
68//! $ tail -f dcc.log
69//! Hello, world!
70//! ```
71//!
72//! ### SEGGER J-Link
73//!
74//! Run J-Link:
75//!
76//! ```console
77//! $ JLinkExe
78//! SEGGER J-Link Commander V9.48 (Compiled Jun  3 2026 14:21:00)
79//! DLL version V9.48, compiled Jun  3 2026 14:20:18
80//!
81//! Connecting to J-Link ...O.K.
82//!
83//! Type "connect" to establish a target connection, '?' for help
84//! J-Link>device LPC2138
85//! J-Link>si JTAG
86//! J-Link>speed 1000
87//! J-Link>jtagconf -1,-1
88//! J-Link>connect
89//! J-Link>r
90//! J-Link>h
91//! J-Link>loadfile target/file.hex
92//! J-Link>go
93//! J-Link>term
94//! Please select terminal protocol:
95//! B) Binary (raw) data (Default)
96//! D) SEGGER DCC terminal
97//! Protocol>D
98//! Hello, world!
99//! ```
100//!
101//! The `term` command activates the DCC terminal. Select 'D' for a DCC
102//! terminal. We don't implement the SEGGER DCC Terminal protocol, but JLink
103//! doesn't seem to mind.
104//!
105//! # Supported Rust version
106//!
107//! - Rust >=1.59
108//!
109//! # Optional features
110//!
111//! ## `nop`
112//!
113//! Turns `dcc::write` into a "no-operation" (not the instruction). This is
114//! useful when the DCC is disabled as `dcc::write` blocks forever in that case.
115//!
116//! ## `legacy-mode`
117//!
118//! By default this crate uses the ARMv7 DCC registers (when `target_arch =
119//! "arm"`). This feature selects the debug registers for the ARM7TDMI and
120//! ARM9EJ-S instead.
121
122#![deny(missing_docs)]
123#![no_std]
124
125use core::fmt;
126
127/// Macro for printing to the DCC
128#[macro_export]
129macro_rules! dprint {
130    ($s:expr) => {
131        $crate::write_str($s)
132    };
133    ($($tt:tt)*) => {
134        $crate::write_fmt(format_args!($($tt)*))
135    };
136}
137
138/// Macro for printing to the DCC, with a newline.
139#[macro_export]
140macro_rules! dprintln {
141    () => {
142        $crate::write_str("\n")
143    };
144    ($s:expr) => {
145        $crate::write_str(concat!($s, "\n"))
146    };
147    ($s:expr, $($tt:tt)*) => {
148        $crate::write_fmt(format_args!(concat!($s, "\n"), $($tt)*))
149    };
150}
151
152/// Proxy struct that implements the `fmt::Write`
153///
154/// The main use case for this is using the `write!` macro
155pub struct Writer;
156
157impl fmt::Write for Writer {
158    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
159        write_str(s);
160        Ok(())
161    }
162}
163
164/// Writes a single word to the DCC
165///
166/// **NOTE:** This operation is blocking
167#[allow(unused_variables)]
168#[inline(always)]
169pub fn write(word: u32) {
170    match () {
171        #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
172        () => unimplemented!(),
173        #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "nop"))]
174        () => {}
175        // See Arm ARM for R-profile AArch64 architecture, section E4.2 DCC and ITR registers for details
176        #[cfg(all(target_arch = "aarch64", not(feature = "nop")))]
177        () => {
178            // "External Debug Status and Control Register (EDSCR) is architecturally mapped to
179            // register MDSCR_EL1"
180            const EDSCR_TXFULL: u64 = 1 << 29;
181
182            // busy wait until the TX FIFO buffer is not full
183            loop {
184                let mut edscr: u64;
185                // MDSCR = Monitor Debug System Control Register
186                unsafe { core::arch::asm!("MRS {}, MDSCR_EL1", out(reg) edscr) }
187                // if EDSCR_TXFULL is 0 we can proceed
188                if edscr & EDSCR_TXFULL == 0 {
189                    break;
190                }
191            }
192            // DBGDTRTX = Debug Data Transfer Register, Transmit
193            unsafe { core::arch::asm!("MSR DBGDTRTX_EL0, {}", in(reg) word as u64) }
194        }
195        #[cfg(all(
196            target_arch = "arm",
197            not(feature = "nop"),
198            not(feature = "legacy-mode")
199        ))]
200        () => {
201            // The DBGDSCR.TXfull bit
202            const DBGDSCR_TXFULL: u32 = 1 << 29;
203
204            unsafe {
205                let mut r: u32;
206                // busy wait until we can send data
207                loop {
208                    // Read DBGDSCR
209                    core::arch::asm!("MRC p14, 0, {}, c0, c1, 0", out(reg) r);
210                    if r & DBGDSCR_TXFULL == 0 {
211                        break;
212                    }
213                }
214                // ARMv7 DBGDTRTX
215                core::arch::asm!("MCR p14, 0, {}, c0, c5, 0", in(reg) word);
216            }
217        }
218        #[cfg(all(target_arch = "arm", not(feature = "nop"), feature = "legacy-mode"))]
219        () => {
220            const DCR_W: u32 = 1 << 1;
221
222            // busy wait until we can send data
223            unsafe {
224                let mut r: u32;
225                loop {
226                    // Read Communications Channel Control Register
227                    core::arch::asm!("MRC p14, 0, {}, c0, c0, 0", out(reg) r);
228                    // "the processor must poll until W=0"
229                    if r & DCR_W == 0 {
230                        break;
231                    }
232                }
233                core::arch::asm!("MCR p14, 0, {}, c1, c0, 0", in(reg) word);
234            }
235        }
236    }
237}
238
239/// Writes the bytes to the DCC
240///
241/// NOTE: each byte will be word-extended before being `write`-n to the DCC
242pub fn write_all(bytes: &[u8]) {
243    // Send raw bytes
244    bytes.iter().for_each(|byte| write(u32::from(*byte)))
245}
246
247#[doc(hidden)]
248pub fn write_fmt(args: fmt::Arguments) {
249    use core::fmt::Write;
250
251    Writer.write_fmt(args).ok();
252}
253
254/// Writes the string to the DCC
255pub fn write_str(string: &str) {
256    write_all(string.as_bytes())
257}