├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── bors.toml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── probe-rs-rtt ├── Cargo.toml └── src │ ├── channel.rs │ ├── channels.rs │ ├── lib.rs │ └── rtt.rs └── rtthost ├── Cargo.toml └── src └── main.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. ... 16 | 2. ... 17 | 3. ... 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Stacktrace** 23 | If applicable, add a stacktrace to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - macOS Mojave 27 | - Windows 10 28 | - Linux (Ubuntu, Gentoo, Debian, Fedora, etc.) 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: Yatekii 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Thank you! 2 | 3 | Thank you for your contribution. 4 | Please make sure that your submission includes the following: 5 | 6 | ### Must 7 | 8 | - [ ] The code compiles without `errors` or `warnings`. 9 | - [ ] All tests pass and in the best case you also added new tests. 10 | - [ ] `cargo fmt` was run. 11 | - [ ] Your changes were added to the `CHANGELOG.md` in the proper section. 12 | 13 | ### Nice to have 14 | 15 | - [ ] You add a description of your work to this PR. 16 | - [ ] You added proper docs (in code, rustdoc and README.md) for your newly added features and code. -------------------------------------------------------------------------------- /.github/bors.toml: -------------------------------------------------------------------------------- 1 | required_approvals = 1 2 | block_labels = ["wip"] 3 | delete_merged_branches = true 4 | status = [ 5 | "Check (ubuntu-latest)", 6 | "Test Suite", 7 | "Rustfmt", 8 | ] -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md 2 | # 3 | # Currently only builds on Linux 4 | 5 | on: 6 | push: 7 | branches: [master, staging, trying] 8 | pull_request: 9 | 10 | name: CI 11 | 12 | jobs: 13 | check: 14 | name: Check 15 | strategy: 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - windows-latest 20 | 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - name: Checkout sources 25 | uses: actions/checkout@v1 26 | 27 | - name: Install libusb 28 | run: sudo apt install -y libusb-1.0-0-dev libftdi1-dev 29 | # Only install on Ubuntu 30 | if: matrix.os == 'ubuntu-latest' 31 | 32 | - name: Install libftdi (windows) 33 | uses: lukka/run-vcpkg@v5 34 | with: 35 | vcpkgArguments: "libftdi1:x64-windows" 36 | # Version including bugfix for libusb (see https://github.com/microsoft/vcpkg/issues/12642) 37 | vcpkgGitCommitId: "6ed0bc982bf9bdca25439d538f7f272786a7af4d" 38 | vcpkgTriplet: x64-windows 39 | if: matrix.os == 'windows-latest' 40 | 41 | - name: Install stable toolchain 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | toolchain: stable 45 | override: true 46 | 47 | - name: Run cargo check 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: check 51 | 52 | test: 53 | name: Test Suite 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Checkout sources 57 | uses: actions/checkout@v1 58 | 59 | - name: Install libusb 60 | run: sudo apt install -y libusb-1.0-0-dev libftdi1-dev 61 | 62 | - name: Install stable toolchain 63 | uses: actions-rs/toolchain@v1 64 | with: 65 | toolchain: stable 66 | override: true 67 | 68 | - name: Run cargo test 69 | uses: actions-rs/cargo@v1 70 | with: 71 | command: test 72 | 73 | fmt: 74 | name: Rustfmt 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout sources 78 | uses: actions/checkout@v1 79 | 80 | - name: Install stable toolchain 81 | uses: actions-rs/toolchain@v1 82 | with: 83 | toolchain: stable 84 | override: true 85 | 86 | - name: Install rustfmt 87 | run: rustup component add rustfmt 88 | 89 | - name: Run cargo fmt 90 | uses: actions-rs/cargo@v1 91 | with: 92 | command: fmt 93 | args: --all -- --check 94 | 95 | clippy: 96 | name: Clippy 97 | runs-on: ubuntu-latest 98 | steps: 99 | - name: Checkout sources 100 | uses: actions/checkout@v1 101 | 102 | - name: Install libusb 103 | run: sudo apt install -y libusb-1.0-0-dev libftdi1-dev 104 | 105 | - name: Install stable toolchain 106 | uses: actions-rs/toolchain@v1 107 | with: 108 | toolchain: stable 109 | override: true 110 | 111 | - name: Install clippy 112 | run: rustup component add clippy 113 | 114 | - name: Run cargo clippy 115 | uses: actions-rs/clippy-check@v1 116 | with: 117 | token: ${{ secrets.GITHUB_TOKEN }} 118 | args: --all-features 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | scratch.txt 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | 8 | ### Added 9 | 10 | ### Changed 11 | 12 | ### Fixed 13 | 14 | ## [0.11.0] 15 | 16 | ### Changed 17 | 18 | - Updated to probe-rs 0.11.0 19 | 20 | ## [0.10.1] 21 | 22 | ### Fixed 23 | 24 | - Fixed a bug where RTT pointers could be torn because of 8bit reads instead of 32bit reads. 25 | 26 | ## [0.10.0] 27 | 28 | ### Changed 29 | 30 | - Updated to probe-rs 0.10.0 31 | 32 | ## [0.4.0] 33 | 34 | ### Added 35 | 36 | - Added more logs on all levels. 37 | 38 | ### Changed 39 | 40 | ### Fixed 41 | 42 | - Fixed a bug where RTT would deadlock. 43 | 44 | ## [0.3.0] 45 | 46 | ### Added 47 | 48 | - Added a proper warning if no RTT channels are found to be configured. 49 | 50 | ### Changed 51 | 52 | ### Fixed 53 | 54 | - Fixed some error in the docs. 55 | 56 | [Unreleased]: https://github.com/probe-rs/probe-rs/compare/v0.11.0...master 57 | [0.10.1]: https://github.com/probe-rs/probe-rs/compare/v0.10.1...v0.11.0 58 | [0.10.1]: https://github.com/probe-rs/probe-rs/compare/v0.10.0...v0.10.1 59 | [0.10.0]: https://github.com/probe-rs/probe-rs/compare/v0.4.0...v0.10.0 60 | [0.4.0]: https://github.com/probe-rs/probe-rs/compare/v0.3.0...v0.4.0 61 | [0.3.0]: https://github.com/probe-rs/probe-rs/releases/tag/v0.3.0 62 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "probe-rs-rtt", 4 | "rtthost", 5 | ] 6 | 7 | [patch.crates-io] 8 | probe-rs = { git = "https://github.com/probe-rs/probe-rs" } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matti Virkkunen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # probe-rs-rtt 2 | 3 | ## INFO: This repository was moved into [probe-rs](https://github.com/probe-rs/probe-rs). 4 | 5 | [![crates.io](https://meritbadge.herokuapp.com/probe-rs-rtt)](https://crates.io/crates/probe-rs-rtt) [![documentation](https://docs.rs/probe-rs-rtt/badge.svg)](https://docs.rs/probe-rs-rtt) 6 | 7 | Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs. 8 | 9 | ## [Documentation](https://docs.rs/probe-rs-rtt) 10 | 11 | RTT implements input and output to/from a microcontroller using in-memory ring buffers and memory polling. This enables debug logging from the microcontroller with minimal delays and no blocking, making it usable even in real-time applications where e.g. semihosting delays cannot be tolerated. 12 | 13 | This crate enables you to read and write via RTT channels. It's also used as a building-block for probe-rs debugging tools. 14 | -------------------------------------------------------------------------------- /probe-rs-rtt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "probe-rs-rtt" 3 | description = "Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs" 4 | version = "0.11.0" 5 | edition = "2018" 6 | readme = "../README.md" 7 | keywords = ["embedded", "debugging", "rtt"] 8 | license = "MIT" 9 | authors = ["Matti Virkkunen "] 10 | repository = "https://github.com/probe-rs/probe-rs-rtt" 11 | 12 | [dependencies] 13 | log = "0.4.8" 14 | probe-rs = { version = "0.11.0", git = "https://github.com/probe-rs/probe-rs" } 15 | scroll = "0.10.1" 16 | thiserror = "1.0.11" 17 | -------------------------------------------------------------------------------- /probe-rs-rtt/src/channel.rs: -------------------------------------------------------------------------------- 1 | use probe_rs::{config::MemoryRegion, MemoryInterface, Session}; 2 | use scroll::{Pread, LE}; 3 | use std::cmp::min; 4 | use std::io; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use crate::Error; 8 | 9 | /// Trait for channel information shared between up and down channels. 10 | pub trait RttChannel { 11 | /// Returns the number of the channel. 12 | fn number(&self) -> usize; 13 | 14 | /// Returns the name of the channel or `None` if there is none. 15 | fn name(&self) -> Option<&str>; 16 | 17 | /// Returns the buffer size in bytes. Note that the usable size is one byte less due to how the 18 | /// ring buffer is implemented. 19 | fn buffer_size(&self) -> usize; 20 | } 21 | 22 | #[derive(Debug)] 23 | pub(crate) struct Channel { 24 | session: Arc>, 25 | number: usize, 26 | ptr: u32, 27 | name: Option, 28 | buffer_ptr: u32, 29 | size: u32, 30 | } 31 | 32 | // Chanels must follow this data layout when reading/writing memory in order to be compatible with 33 | // the official RTT implementation. 34 | // 35 | // struct Channel { 36 | // const char *name; // Name of channel, pointer to null-terminated string. Optional. 37 | // char *buffer; // Pointer to buffer data 38 | // unsigned int size; // Size of data buffer. The actual capacity is one byte less. 39 | // unsigned int write; // Offset in data buffer of next byte to write. 40 | // unsigned int read; // Offset in data buffer of next byte to read. 41 | // // The low 2 bits of flags are used for blocking/non blocking modes, the rest are ignored. 42 | // unsigned int flags; 43 | // } 44 | 45 | impl Channel { 46 | // Size of the Channel struct in target memory in bytes 47 | pub(crate) const SIZE: usize = 24; 48 | 49 | // Offsets of fields in target memory in bytes 50 | const O_NAME: usize = 0; 51 | const O_BUFFER_PTR: usize = 4; 52 | const O_SIZE: usize = 8; 53 | const O_WRITE: usize = 12; 54 | const O_READ: usize = 16; 55 | const O_FLAGS: usize = 20; 56 | 57 | pub(crate) fn from( 58 | session: &Arc>, 59 | number: usize, 60 | memory_map: &[MemoryRegion], 61 | ptr: u32, 62 | mem: &[u8], 63 | ) -> Result, Error> { 64 | let buffer_ptr: u32 = mem.pread_with(Self::O_BUFFER_PTR, LE).unwrap(); 65 | if buffer_ptr == 0 { 66 | // This buffer isn't in use 67 | return Ok(None); 68 | } 69 | 70 | let name_ptr: u32 = mem.pread_with(Self::O_NAME, LE).unwrap(); 71 | 72 | let name = if name_ptr == 0 { 73 | None 74 | } else { 75 | read_c_string(&mut session.lock().unwrap(), memory_map, name_ptr)? 76 | }; 77 | 78 | Ok(Some(Channel { 79 | session: Arc::clone(session), 80 | number, 81 | ptr, 82 | name, 83 | buffer_ptr, 84 | size: mem.pread_with(Self::O_SIZE, LE).unwrap(), 85 | })) 86 | } 87 | 88 | pub fn name(&self) -> Option<&str> { 89 | self.name.as_ref().map(|s| s.as_ref()) 90 | } 91 | 92 | pub fn buffer_size(&self) -> usize { 93 | self.size as usize 94 | } 95 | 96 | fn read_pointers(&self, dir: &'static str) -> Result<(u32, u32), Error> { 97 | let mut block = [0u32; 2]; 98 | self.session 99 | .lock() 100 | .unwrap() 101 | .core(0)? 102 | .read_32(self.ptr + Self::O_WRITE as u32, block.as_mut())?; 103 | 104 | let write: u32 = block[0]; 105 | let read: u32 = block[1]; 106 | 107 | let validate = |which, value| { 108 | if value >= self.size { 109 | Err(Error::ControlBlockCorrupted(format!( 110 | "{} pointer is {} while buffer size is {} for {:?} channel {} ({})", 111 | which, 112 | value, 113 | self.size, 114 | dir, 115 | self.number, 116 | self.name().unwrap_or("no name"), 117 | ))) 118 | } else { 119 | Ok(()) 120 | } 121 | }; 122 | 123 | validate("write", write)?; 124 | validate("read", read)?; 125 | 126 | Ok((write, read)) 127 | } 128 | } 129 | 130 | /// RTT up (target to host) channel. 131 | #[derive(Debug)] 132 | pub struct UpChannel(pub(crate) Channel); 133 | 134 | impl UpChannel { 135 | /// Returns the number of the channel. 136 | pub fn number(&self) -> usize { 137 | self.0.number 138 | } 139 | 140 | /// Returns the name of the channel or `None` if there is none. 141 | pub fn name(&self) -> Option<&str> { 142 | self.0.name() 143 | } 144 | 145 | /// Returns the buffer size in bytes. Note that the usable size is one byte less due to how the 146 | /// ring buffer is implemented. 147 | pub fn buffer_size(&self) -> usize { 148 | self.0.buffer_size() 149 | } 150 | 151 | /// Reads the current channel mode from the target and returns its. 152 | /// 153 | /// See [`ChannelMode`] for more information on what the modes mean. 154 | pub fn mode(&self) -> Result { 155 | let mut lock = self.0.session.lock().unwrap(); 156 | let mut core = lock.core(0)?; 157 | let flags = core.read_word_32(self.0.ptr + Channel::O_FLAGS as u32)?; 158 | 159 | match flags & 0x3 { 160 | 0 => Ok(ChannelMode::NoBlockSkip), 161 | 1 => Ok(ChannelMode::NoBlockTrim), 162 | 2 => Ok(ChannelMode::BlockIfFull), 163 | _ => Err(Error::ControlBlockCorrupted(String::from( 164 | "The channel mode flags are invalid", 165 | ))), 166 | } 167 | } 168 | 169 | /// Changes the channel mode on the target to the specified mode. 170 | /// 171 | /// See [`ChannelMode`] for more information on what the modes mean. 172 | pub fn set_mode(&self, mode: ChannelMode) -> Result<(), Error> { 173 | let mut lock = self.0.session.lock().unwrap(); 174 | let mut core = lock.core(0)?; 175 | 176 | let flags = core.read_word_32(self.0.ptr + Channel::O_FLAGS as u32)?; 177 | 178 | let new_flags = (flags & !3) | (mode as u32); 179 | core.write_word_32(self.0.ptr + Channel::O_FLAGS as u32, new_flags)?; 180 | 181 | Ok(()) 182 | } 183 | 184 | fn read_core(&self, mut buf: &mut [u8]) -> Result<(u32, usize), Error> { 185 | let (write, mut read) = self.0.read_pointers("up")?; 186 | 187 | let mut total = 0; 188 | 189 | // Read while buffer contains data and output buffer has space (maximum of two iterations) 190 | while buf.len() > 0 { 191 | let count = min(self.readable_contiguous(write, read), buf.len()); 192 | if count == 0 { 193 | break; 194 | } 195 | 196 | let mut lock = self.0.session.lock().unwrap(); 197 | let mut core = lock.core(0)?; 198 | core.read(self.0.buffer_ptr + read, &mut buf[..count])?; 199 | 200 | total += count; 201 | read += count as u32; 202 | 203 | if read >= self.0.size { 204 | // Wrap around to start 205 | read = 0; 206 | } 207 | 208 | buf = &mut buf[count..]; 209 | } 210 | 211 | Ok((read, total)) 212 | } 213 | 214 | /// Reads some bytes from the channel to the specified buffer and returns how many bytes were 215 | /// read. 216 | /// 217 | /// This method will not block waiting for data in the target buffer, and may read less bytes 218 | /// than would fit in `buf`. 219 | pub fn read(&self, buf: &mut [u8]) -> Result { 220 | let (read, total) = self.read_core(buf)?; 221 | 222 | if total > 0 { 223 | // Write read pointer back to target if something was read 224 | let mut lock = self.0.session.lock().unwrap(); 225 | let mut core = lock.core(0)?; 226 | core.write_word_32(self.0.ptr + Channel::O_READ as u32, read)?; 227 | } 228 | 229 | Ok(total) 230 | } 231 | 232 | /// Peeks at the current data in the channel buffer, copies data into the specified buffer and 233 | /// returns how many bytes were read. 234 | /// 235 | /// The difference from [`read`](UpChannel::read) is that this does not discard the data in the 236 | /// buffer. 237 | pub fn peek(&self, buf: &mut [u8]) -> Result { 238 | Ok(self.read_core(buf)?.1) 239 | } 240 | 241 | /// Calculates amount of contiguous data available for reading 242 | fn readable_contiguous(&self, write: u32, read: u32) -> usize { 243 | (if read > write { 244 | self.0.size - read 245 | } else { 246 | write - read 247 | }) as usize 248 | } 249 | } 250 | 251 | impl RttChannel for UpChannel { 252 | /// Returns the number of the channel. 253 | fn number(&self) -> usize { 254 | self.0.number 255 | } 256 | 257 | fn name(&self) -> Option<&str> { 258 | self.0.name() 259 | } 260 | fn buffer_size(&self) -> usize { 261 | self.0.buffer_size() 262 | } 263 | } 264 | 265 | impl io::Read for UpChannel { 266 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 267 | UpChannel::read(self, buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 268 | } 269 | } 270 | 271 | /// RTT down (host to target) channel. 272 | #[derive(Debug)] 273 | pub struct DownChannel(pub(crate) Channel); 274 | 275 | impl DownChannel { 276 | /// Returns the number of the channel. 277 | pub fn number(&self) -> usize { 278 | self.0.number 279 | } 280 | 281 | /// Returns the name of the channel or `None` if there is none. 282 | pub fn name(&self) -> Option<&str> { 283 | self.0.name() 284 | } 285 | 286 | /// Returns the buffer size in bytes. Note that the usable size is one byte less due to how the 287 | /// ring buffer is implemented. 288 | pub fn buffer_size(&self) -> usize { 289 | self.0.buffer_size() 290 | } 291 | 292 | /// Writes some bytes into the channel buffer and returns the number of bytes written. 293 | /// 294 | /// This method will not block waiting for space to become available in the channel buffer, and 295 | /// may not write all of `buf`. 296 | pub fn write(&self, mut buf: &[u8]) -> Result { 297 | let (mut write, read) = self.0.read_pointers("down")?; 298 | 299 | if self.writable_contiguous(write, read) == 0 { 300 | // Buffer is full - do nothing. 301 | return Ok(0); 302 | } 303 | 304 | let mut total = 0; 305 | 306 | // Write while buffer has space for data and output contains data (maximum of two iterations) 307 | while buf.len() > 0 { 308 | let count = min(self.writable_contiguous(write, read), buf.len()); 309 | if count == 0 { 310 | break; 311 | } 312 | 313 | let mut lock = self.0.session.lock().unwrap(); 314 | let mut core = lock.core(0)?; 315 | core.write_8(self.0.buffer_ptr + write, &buf[..count])?; 316 | 317 | total += count; 318 | write += count as u32; 319 | 320 | if write >= self.0.size { 321 | // Wrap around to start 322 | write = 0; 323 | } 324 | 325 | buf = &buf[count..]; 326 | } 327 | 328 | // Write write pointer back to target 329 | 330 | let mut lock = self.0.session.lock().unwrap(); 331 | let mut core = lock.core(0)?; 332 | core.write_word_32(self.0.ptr + Channel::O_WRITE as u32, write)?; 333 | 334 | Ok(total) 335 | } 336 | 337 | /// Calculates amount of contiguous space available for writing 338 | fn writable_contiguous(&self, write: u32, read: u32) -> usize { 339 | (if read > write { 340 | read - write - 1 341 | } else if read == 0 { 342 | self.0.size - write - 1 343 | } else { 344 | self.0.size - write 345 | }) as usize 346 | } 347 | } 348 | 349 | impl RttChannel for DownChannel { 350 | /// Returns the number of the channel. 351 | fn number(&self) -> usize { 352 | self.0.number 353 | } 354 | 355 | fn name(&self) -> Option<&str> { 356 | self.0.name() 357 | } 358 | fn buffer_size(&self) -> usize { 359 | self.0.buffer_size() 360 | } 361 | } 362 | 363 | impl io::Write for DownChannel { 364 | fn write(&mut self, buf: &[u8]) -> io::Result { 365 | DownChannel::write(self, buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 366 | } 367 | 368 | fn flush(&mut self) -> io::Result<()> { 369 | Ok(()) 370 | } 371 | } 372 | 373 | /// Reads a null-terminated string from target memory. Lossy UTF-8 decoding is used. 374 | fn read_c_string( 375 | session: &mut Session, 376 | memory_map: &[MemoryRegion], 377 | ptr: u32, 378 | ) -> Result, Error> { 379 | // Find out which memory range contains the pointer 380 | let range = memory_map 381 | .iter() 382 | .filter_map(|r| match r { 383 | MemoryRegion::Nvm(r) => Some(&r.range), 384 | MemoryRegion::Ram(r) => Some(&r.range), 385 | _ => None, 386 | }) 387 | .find(|r| r.contains(&ptr)); 388 | 389 | // If the pointer is not within any valid range, return None. 390 | let range = match range { 391 | Some(r) => r, 392 | None => return Ok(None), 393 | }; 394 | 395 | // Read up to 128 bytes not going past the end of the region 396 | let mut bytes = vec![0u8; min(128, (range.end - ptr) as usize)]; 397 | session.core(0)?.read_8(ptr, bytes.as_mut())?; 398 | 399 | // If the bytes read contain a null, return the preceding part as a string, otherwise None. 400 | Ok(bytes 401 | .iter() 402 | .position(|&b| b == 0) 403 | .map(|p| String::from_utf8_lossy(&bytes[..p]).into_owned())) 404 | } 405 | 406 | /// Specifies what to do when a channel doesn't have enough buffer space for a complete write on the 407 | /// target side. 408 | #[derive(Eq, PartialEq, Debug)] 409 | #[repr(u32)] 410 | pub enum ChannelMode { 411 | /// Skip writing the data completely if it doesn't fit in its entirety. 412 | NoBlockSkip = 0, 413 | 414 | /// Write as much as possible of the data and ignore the rest. 415 | NoBlockTrim = 1, 416 | 417 | /// Block (spin) if the buffer is full. Note that if the application writes within a critical 418 | /// section, using this mode can cause the application to freeze if the buffer becomes full and 419 | /// is not read by the host. 420 | BlockIfFull = 2, 421 | } 422 | -------------------------------------------------------------------------------- /probe-rs-rtt/src/channels.rs: -------------------------------------------------------------------------------- 1 | //! List of RTT channels. 2 | 3 | use crate::RttChannel; 4 | use std::collections::{btree_map, BTreeMap}; 5 | use std::mem; 6 | 7 | /// List of RTT channels. 8 | #[derive(Debug)] 9 | pub struct Channels(pub(crate) BTreeMap); 10 | 11 | impl Channels { 12 | /// Returns the number of channels on the list. 13 | pub fn len(&self) -> usize { 14 | self.0.len() 15 | } 16 | 17 | /// Returns `true` if the list is empty. 18 | pub fn is_empty(&self) -> bool { 19 | self.0.is_empty() 20 | } 21 | 22 | /// Returns a reference to the channel corresponding to the number. 23 | pub fn get(&mut self, number: usize) -> Option<&T> { 24 | self.0.get(&number) 25 | } 26 | 27 | /// Removes the channel corresponding to the number from the list and returns it. 28 | pub fn take(&mut self, number: usize) -> Option { 29 | self.0.remove(&number) 30 | } 31 | 32 | /// Gets and iterator over the channels on the list, sorted by number. 33 | pub fn iter(&self) -> Iter<'_, T> { 34 | Iter(self.0.iter()) 35 | } 36 | 37 | /// Gets and iterator over the channels on the list, sorted by number. 38 | pub fn drain(&mut self) -> Drain { 39 | let map = mem::replace(&mut self.0, BTreeMap::new()); 40 | 41 | Drain(map.into_iter()) 42 | } 43 | } 44 | 45 | /// An iterator over RTT channels. 46 | /// 47 | /// This struct is created by the [`Channels::iter`] method. See its documentation for more. 48 | pub struct Iter<'a, T: RttChannel>(btree_map::Iter<'a, usize, T>); 49 | 50 | impl<'a, T: RttChannel> Iterator for Iter<'a, T> { 51 | type Item = &'a T; 52 | 53 | fn next(&mut self) -> Option { 54 | self.0.next().map(|(_, v)| v) 55 | } 56 | } 57 | 58 | /// A draining iterator over RTT channels. 59 | /// 60 | /// This struct is created by the [`Channels::drain`] method. See its documentation for more. 61 | pub struct Drain(btree_map::IntoIter); 62 | 63 | impl<'a, T: RttChannel> Iterator for Drain { 64 | type Item = T; 65 | 66 | fn next(&mut self) -> Option { 67 | self.0.next().map(|(_, v)| v) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /probe-rs-rtt/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs 2 | //! 3 | //! RTT implements input and output to/from a microcontroller using in-memory ring buffers and 4 | //! memory polling. This enables debug logging from the microcontroller with minimal delays and no 5 | //! blocking, making it usable even in real-time applications where e.g. semihosting delays cannot 6 | //! be tolerated. 7 | //! 8 | //! This crate enables you to read and write via RTT channels. It's also used as a building-block 9 | //! for probe-rs debugging tools. 10 | //! 11 | //! ## Example 12 | //! 13 | //! ```no_run 14 | //! use std::sync::{Arc, Mutex}; 15 | //! use probe_rs::Probe; 16 | //! use probe_rs_rtt::Rtt; 17 | //! 18 | //! // First obtain a probe-rs session (see probe-rs documentation for details) 19 | //! let probe = Probe::list_all()[0].open()?; 20 | //! let mut session = probe.attach("somechip")?; 21 | //! 22 | //! // Attach to RTT 23 | //! let mut rtt = Rtt::attach(Arc::new(Mutex::new(session)))?; 24 | //! 25 | //! // Read from a channel 26 | //! if let Some(input) = rtt.up_channels().take(0) { 27 | //! let mut buf = [0u8; 1024]; 28 | //! let count = input.read(&mut buf[..])?; 29 | //! 30 | //! println!("Read data: {:?}", &buf[..count]); 31 | //! } 32 | //! 33 | //! // Write to a channel 34 | //! if let Some(output) = rtt.down_channels().take(0) { 35 | //! output.write(b"Hello, computer!\n")?; 36 | //! } 37 | //! 38 | //! # Ok::<(), Box>(()) 39 | //! ``` 40 | 41 | use thiserror::Error; 42 | 43 | mod channel; 44 | pub use channel::*; 45 | 46 | pub mod channels; 47 | pub use channels::Channels; 48 | 49 | mod rtt; 50 | pub use rtt::*; 51 | 52 | /// Error type for RTT operations. 53 | #[derive(Error, Debug)] 54 | pub enum Error { 55 | /// RTT control block not found in target memory. Make sure RTT is initialized on the target. 56 | #[error( 57 | "RTT control block not found in target memory.\n\ 58 | - Make sure RTT is initialized on the target.\n\ 59 | - Depending on the target, sleep modes can interfere with RTT." 60 | )] 61 | ControlBlockNotFound, 62 | 63 | /// Multiple control blocks found in target memory. The data contains the control block addresses (up to 5). 64 | #[error("Multiple control blocks found in target memory.")] 65 | MultipleControlBlocksFound(Vec), 66 | 67 | /// The control block has been corrupted. The data contains a detailed error. 68 | #[error("Control block corrupted: {0}")] 69 | ControlBlockCorrupted(String), 70 | 71 | /// Wraps errors propagated up from probe-rs. 72 | #[error("Error communicating with probe: {0}")] 73 | Probe(#[from] probe_rs::Error), 74 | } 75 | -------------------------------------------------------------------------------- /probe-rs-rtt/src/rtt.rs: -------------------------------------------------------------------------------- 1 | use probe_rs::{config::MemoryRegion, MemoryInterface, Session}; 2 | use scroll::{Pread, LE}; 3 | use std::borrow::Cow; 4 | use std::collections::BTreeMap; 5 | use std::ops::Range; 6 | use std::sync::{Arc, Mutex}; 7 | 8 | use crate::channel::*; 9 | use crate::{Channels, Error}; 10 | 11 | /// The RTT interface. 12 | /// 13 | /// Use [`Rtt::attach`] to attach to a probe-rs `Core` and detect channels. 14 | #[derive(Debug)] 15 | pub struct Rtt { 16 | ptr: u32, 17 | up_channels: Channels, 18 | down_channels: Channels, 19 | } 20 | 21 | // Rtt must follow this data layout when reading/writing memory in order to be compatible with the 22 | // official RTT implementation. 23 | // 24 | // struct ControlBlock { 25 | // char id[16]; // Used to find/validate the control block. 26 | // // Maximum number of up (target to host) channels in following array 27 | // unsigned int max_up_channels; 28 | // // Maximum number of down (host to target) channels in following array. 29 | // unsigned int max_down_channels; 30 | // RttChannel up_channels[max_up_channels]; // Array of up (target to host) channels. 31 | // RttChannel down_channels[max_down_channels]; // array of down (host to target) channels. 32 | // } 33 | 34 | impl Rtt { 35 | const RTT_ID: [u8; 16] = *b"SEGGER RTT\0\0\0\0\0\0"; 36 | 37 | // Minimum size of the ControlBlock struct in target memory in bytes with empty arrays 38 | const MIN_SIZE: usize = Self::O_CHANNEL_ARRAYS; 39 | 40 | // Offsets of fields in target memory in bytes 41 | const O_ID: usize = 0; 42 | const O_MAX_UP_CHANNELS: usize = 16; 43 | const O_MAX_DOWN_CHANNELS: usize = 20; 44 | const O_CHANNEL_ARRAYS: usize = 24; 45 | 46 | fn from( 47 | session: Arc>, 48 | memory_map: &[MemoryRegion], 49 | // Pointer from which to scan 50 | ptr: u32, 51 | // Memory contents read in advance, starting from ptr 52 | mem_in: Option<&[u8]>, 53 | ) -> Result, Error> { 54 | let mut mem = match mem_in { 55 | Some(mem) => Cow::Borrowed(mem), 56 | None => { 57 | // If memory wasn't passed in, read the minimum header size 58 | let mut mem = vec![0u8; Self::MIN_SIZE]; 59 | let mut lock = session.lock().unwrap(); 60 | let mut core = lock.core(0)?; 61 | core.read_8(ptr, &mut mem)?; 62 | Cow::Owned(mem) 63 | } 64 | }; 65 | 66 | // Validate that the control block starts with the ID bytes 67 | let rtt_id = &mem[Self::O_ID..(Self::O_ID + Self::RTT_ID.len())]; 68 | if rtt_id != Self::RTT_ID { 69 | log::trace!( 70 | "Expected control block to start with RTT ID. Got instead: {:?}", 71 | rtt_id 72 | ); 73 | 74 | return Ok(None); 75 | } 76 | 77 | let max_up_channels = mem.pread_with::(Self::O_MAX_UP_CHANNELS, LE).unwrap() as usize; 78 | let max_down_channels = mem 79 | .pread_with::(Self::O_MAX_DOWN_CHANNELS, LE) 80 | .unwrap() as usize; 81 | 82 | // *Very* conservative sanity check, most people 83 | if max_up_channels > 255 || max_down_channels > 255 { 84 | return Err(Error::ControlBlockCorrupted(format!( 85 | "Nonsensical array sizes at {:08x}: max_up_channels={} max_down_channels={}", 86 | ptr, max_up_channels, max_down_channels 87 | ))); 88 | } 89 | 90 | let cb_len = Self::O_CHANNEL_ARRAYS + (max_up_channels + max_down_channels) * Channel::SIZE; 91 | 92 | if let Cow::Owned(mem) = &mut mem { 93 | // If memory wasn't passed in, read the rest of the control block 94 | mem.resize(cb_len, 0); 95 | let mut lock = session.lock().unwrap(); 96 | let mut core = lock.core(0)?; 97 | core.read_8( 98 | ptr + Self::MIN_SIZE as u32, 99 | &mut mem[Self::MIN_SIZE..cb_len], 100 | )?; 101 | } 102 | 103 | // Validate that the entire control block fits within the region 104 | if mem.len() < cb_len { 105 | log::debug!("Control block doesn't fit in scanned memory region."); 106 | return Ok(None); 107 | } 108 | 109 | let mut up_channels = BTreeMap::new(); 110 | let mut down_channels = BTreeMap::new(); 111 | 112 | for i in 0..max_up_channels { 113 | let offset = Self::O_CHANNEL_ARRAYS + i * Channel::SIZE; 114 | 115 | if let Some(chan) = 116 | Channel::from(&session, i, memory_map, ptr + offset as u32, &mem[offset..])? 117 | { 118 | up_channels.insert(i, UpChannel(chan)); 119 | } else { 120 | log::warn!("Buffer for up channel {} not initialized", i); 121 | } 122 | } 123 | 124 | for i in 0..max_down_channels { 125 | let offset = 126 | Self::O_CHANNEL_ARRAYS + (max_up_channels * Channel::SIZE) + i * Channel::SIZE; 127 | 128 | if let Some(chan) = 129 | Channel::from(&session, i, memory_map, ptr + offset as u32, &mem[offset..])? 130 | { 131 | down_channels.insert(i, DownChannel(chan)); 132 | } else { 133 | log::warn!("Buffer for down channel {} not initialized", i); 134 | } 135 | } 136 | 137 | Ok(Some(Rtt { 138 | ptr, 139 | up_channels: Channels(up_channels), 140 | down_channels: Channels(down_channels), 141 | })) 142 | } 143 | 144 | /// Attempts to detect an RTT control block anywhere in the target RAM and returns an instance 145 | /// if a valid control block was found. 146 | /// 147 | /// `core` can be e.g. an owned `Core` or a shared `Rc`. The session is only borrowed 148 | /// temporarily during detection. 149 | pub fn attach(session: Arc>) -> Result { 150 | Self::attach_region(session, &Default::default()) 151 | } 152 | 153 | /// Attempts to detect an RTT control block in the specified RAM region(s) and returns an 154 | /// instance if a valid control block was found. 155 | /// 156 | /// `core` can be e.g. an owned `Core` or a shared `Rc`. The session is only borrowed 157 | /// temporarily during detection. 158 | pub fn attach_region(session: Arc>, region: &ScanRegion) -> Result { 159 | let memory_map: &[MemoryRegion] = &session.lock().unwrap().memory_map().to_vec(); 160 | 161 | let ranges: Vec> = match region { 162 | ScanRegion::Exact(addr) => { 163 | log::debug!("Scanning at exact address: 0x{:X}", addr); 164 | 165 | return Rtt::from(session, memory_map, *addr, None)? 166 | .ok_or(Error::ControlBlockNotFound); 167 | } 168 | ScanRegion::Ram => { 169 | log::debug!("Scanning RAM"); 170 | 171 | memory_map 172 | .iter() 173 | .filter_map(|r| match r { 174 | MemoryRegion::Ram(r) => Some(r.range.clone()), 175 | _ => None, 176 | }) 177 | .collect() 178 | } 179 | ScanRegion::Range(region) => { 180 | log::debug!("Scanning region: {:?}", region); 181 | 182 | vec![region.clone()] 183 | } 184 | }; 185 | 186 | let mut mem: Vec = Vec::new(); 187 | let mut instances: Vec = Vec::new(); 188 | 189 | for range in ranges.iter() { 190 | if range.len() < Self::MIN_SIZE { 191 | continue; 192 | } 193 | 194 | mem.resize(range.len(), 0); 195 | { 196 | let mut lock = session.lock().unwrap(); 197 | let mut core = lock.core(0)?; 198 | core.read_8(range.start, mem.as_mut())?; 199 | } 200 | 201 | for offset in 0..(mem.len() - Self::MIN_SIZE) { 202 | if let Some(rtt) = Rtt::from( 203 | session.clone(), 204 | memory_map, 205 | range.start + offset as u32, 206 | Some(&mem[offset..]), 207 | )? { 208 | instances.push(rtt); 209 | 210 | if instances.len() >= 5 { 211 | break; 212 | } 213 | } 214 | } 215 | } 216 | 217 | if instances.len() == 0 { 218 | return Err(Error::ControlBlockNotFound); 219 | } 220 | 221 | if instances.len() > 1 { 222 | return Err(Error::MultipleControlBlocksFound( 223 | instances.into_iter().map(|i| i.ptr).collect(), 224 | )); 225 | } 226 | 227 | Ok(instances.remove(0)) 228 | } 229 | 230 | /// Returns the memory address of the control block in target memory. 231 | pub fn ptr(&self) -> u32 { 232 | self.ptr 233 | } 234 | 235 | /// Gets the detected up channels. 236 | pub fn up_channels(&mut self) -> &mut Channels { 237 | &mut self.up_channels 238 | } 239 | 240 | /// Gets the detected down channels. 241 | pub fn down_channels(&mut self) -> &mut Channels { 242 | &mut self.down_channels 243 | } 244 | } 245 | 246 | /// Used to specify which memory regions to scan for the RTT control block. 247 | #[derive(Clone, Debug)] 248 | pub enum ScanRegion { 249 | /// Scans all RAM regions known to probe-rs. This is the default and should always work, however 250 | /// if your device has a lot of RAM, scanning all of it is slow. 251 | Ram, 252 | 253 | /// Limit scanning to these memory addresses in target memory. It is up to the user to ensure 254 | /// that reading from this range will not read from undefined memory. 255 | Range(Range), 256 | 257 | /// Tries to find the control block starting at this exact address. It is up to the user to 258 | /// ensure that reading the necessary bytes after the pointer will no read from undefined 259 | /// memory. 260 | Exact(u32), 261 | } 262 | 263 | impl Default for ScanRegion { 264 | fn default() -> Self { 265 | ScanRegion::Ram 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /rtthost/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtthost" 3 | version = "0.11.0" 4 | edition = "2018" 5 | license = "MIT" 6 | authors = ["Matti Virkkunen "] 7 | 8 | [dependencies] 9 | pretty_env_logger = "0.4.0" 10 | probe-rs = { version = "0.11.0", git = "https://github.com/probe-rs/probe-rs" } 11 | probe-rs-rtt = { version = "0.11.0", path = "../probe-rs-rtt" } 12 | structopt = "0.3.11" 13 | -------------------------------------------------------------------------------- /rtthost/src/main.rs: -------------------------------------------------------------------------------- 1 | use probe_rs::{config::TargetSelector, DebugProbeInfo, Probe}; 2 | use probe_rs_rtt::{Channels, Rtt, RttChannel, ScanRegion}; 3 | use std::io::prelude::*; 4 | use std::io::{stdin, stdout}; 5 | use std::sync::{ 6 | mpsc::{channel, Receiver}, 7 | Arc, Mutex, 8 | }; 9 | use std::thread; 10 | use structopt::StructOpt; 11 | 12 | #[derive(Debug, PartialEq, Eq)] 13 | enum ProbeInfo { 14 | Number(usize), 15 | List, 16 | } 17 | 18 | impl std::str::FromStr for ProbeInfo { 19 | type Err = &'static str; 20 | 21 | fn from_str(s: &str) -> Result { 22 | if s == "list" { 23 | Ok(ProbeInfo::List) 24 | } else if let Ok(n) = s.parse::() { 25 | Ok(ProbeInfo::Number(n)) 26 | } else { 27 | Err("Invalid probe number.") 28 | } 29 | } 30 | } 31 | 32 | fn parse_scan_region(mut src: &str) -> Result> { 33 | src = src.trim(); 34 | if src.is_empty() { 35 | return Ok(ScanRegion::Ram); 36 | } 37 | 38 | let parts = src 39 | .split("..") 40 | .map(|p| { 41 | if p.starts_with("0x") || p.starts_with("0X") { 42 | u32::from_str_radix(&p[2..], 16) 43 | } else { 44 | p.parse() 45 | } 46 | }) 47 | .collect::, _>>()?; 48 | 49 | match parts.as_slice() { 50 | &[addr] => Ok(ScanRegion::Exact(addr)), 51 | &[start, end] => Ok(ScanRegion::Range(start..end)), 52 | _ => Err("Invalid range: multiple '..'s".into()), 53 | } 54 | } 55 | 56 | #[derive(Debug, StructOpt)] 57 | #[structopt( 58 | name = "rtthost", 59 | about = "Host program for debugging microcontrollers using the RTT (real-time transfer) protocol." 60 | )] 61 | struct Opts { 62 | #[structopt( 63 | short, 64 | long, 65 | default_value = "0", 66 | help = "Specify probe number or 'list' to list probes." 67 | )] 68 | probe: ProbeInfo, 69 | 70 | #[structopt( 71 | short, 72 | long, 73 | help = "Target chip type. Leave unspecified to auto-detect." 74 | )] 75 | chip: Option, 76 | 77 | #[structopt(short, long, help = "List RTT channels and exit.")] 78 | list: bool, 79 | 80 | #[structopt( 81 | short, 82 | long, 83 | help = "Number of up channel to output. Defaults to 0 if it exists." 84 | )] 85 | up: Option, 86 | 87 | #[structopt( 88 | short, 89 | long, 90 | help = "Number of down channel for keyboard input. Defaults to 0 if it exists." 91 | )] 92 | down: Option, 93 | 94 | #[structopt( 95 | long, 96 | default_value="", 97 | parse(try_from_str=parse_scan_region), 98 | help = "Memory region to scan for control block. You can specify either an exact starting address '0x1000' or a range such as '0x0000..0x1000'. Both decimal and hex are accepted.")] 99 | scan_region: ScanRegion, 100 | } 101 | 102 | fn main() { 103 | pretty_env_logger::init(); 104 | 105 | std::process::exit(run()); 106 | } 107 | 108 | fn run() -> i32 { 109 | let opts = Opts::from_args(); 110 | 111 | let probes = Probe::list_all(); 112 | 113 | if probes.len() == 0 { 114 | eprintln!("No debug probes available. Make sure your probe is plugged in, supported and up-to-date."); 115 | return 1; 116 | } 117 | 118 | let probe_number = match opts.probe { 119 | ProbeInfo::List => { 120 | list_probes(std::io::stdout(), &probes); 121 | return 0; 122 | } 123 | ProbeInfo::Number(i) => i, 124 | }; 125 | 126 | if probe_number >= probes.len() { 127 | eprintln!("Probe {} does not exist.", probe_number); 128 | list_probes(std::io::stderr(), &probes); 129 | return 1; 130 | } 131 | 132 | let probe = match probes[probe_number].open() { 133 | Ok(probe) => probe, 134 | Err(err) => { 135 | eprintln!("Error opening probe: {}", err); 136 | return 1; 137 | } 138 | }; 139 | 140 | let target_selector = opts 141 | .chip 142 | .clone() 143 | .map(|t| TargetSelector::Unspecified(t)) 144 | .unwrap_or(TargetSelector::Auto); 145 | 146 | let session = match probe.attach(target_selector) { 147 | Ok(session) => session, 148 | Err(err) => { 149 | eprintln!("Error creating debug session: {}", err); 150 | 151 | if opts.chip.is_none() { 152 | if let probe_rs::Error::ChipNotFound(_) = err { 153 | eprintln!("Hint: Use '--chip' to specify the target chip type manually"); 154 | } 155 | } 156 | 157 | return 1; 158 | } 159 | }; 160 | 161 | eprintln!("Attaching to RTT..."); 162 | 163 | let mut rtt = match Rtt::attach_region(Arc::new(Mutex::new(session)), &opts.scan_region) { 164 | Ok(rtt) => rtt, 165 | Err(err) => { 166 | eprintln!("Error attaching to RTT: {}", err); 167 | return 1; 168 | } 169 | }; 170 | 171 | if opts.list { 172 | println!("Up channels:"); 173 | list_channels(rtt.up_channels()); 174 | 175 | println!("Down channels:"); 176 | list_channels(rtt.down_channels()); 177 | 178 | return 0; 179 | } 180 | 181 | let up_channel = if let Some(up) = opts.up { 182 | let chan = rtt.up_channels().take(up); 183 | 184 | if chan.is_none() { 185 | eprintln!("Error: up channel {} does not exist.", up); 186 | return 1; 187 | } 188 | 189 | chan 190 | } else { 191 | rtt.up_channels().take(0) 192 | }; 193 | 194 | let down_channel = if let Some(down) = opts.down { 195 | let chan = rtt.down_channels().take(down); 196 | 197 | if chan.is_none() { 198 | eprintln!("Error: up channel {} does not exist.", down); 199 | return 1; 200 | } 201 | 202 | chan 203 | } else { 204 | rtt.down_channels().take(0) 205 | }; 206 | 207 | let stdin = down_channel.as_ref().map(|_| stdin_channel()); 208 | 209 | eprintln!("Found control block at 0x{:08x}", rtt.ptr()); 210 | 211 | let mut up_buf = [0u8; 1024]; 212 | let mut down_buf = vec![]; 213 | 214 | loop { 215 | if let Some(up_channel) = up_channel.as_ref() { 216 | let count = match up_channel.read(up_buf.as_mut()) { 217 | Ok(count) => count, 218 | Err(err) => { 219 | eprintln!("\nError reading from RTT: {}", err); 220 | return 1; 221 | } 222 | }; 223 | 224 | match stdout().write_all(&up_buf[..count]) { 225 | Ok(_) => { 226 | stdout().flush().ok(); 227 | } 228 | Err(err) => { 229 | eprintln!("Error writing to stdout: {}", err); 230 | return 1; 231 | } 232 | } 233 | } 234 | 235 | if let (Some(down_channel), Some(stdin)) = (down_channel.as_ref(), &stdin) { 236 | if let Ok(bytes) = stdin.try_recv() { 237 | down_buf.extend_from_slice(bytes.as_slice()); 238 | } 239 | 240 | if !down_buf.is_empty() { 241 | let count = match down_channel.write(down_buf.as_mut()) { 242 | Ok(count) => count, 243 | Err(err) => { 244 | eprintln!("\nError writing to RTT: {}", err); 245 | return 1; 246 | } 247 | }; 248 | 249 | if count > 0 { 250 | down_buf.drain(..count); 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | fn list_probes(mut stream: impl std::io::Write, probes: &Vec) { 258 | writeln!(stream, "Available probes:").unwrap(); 259 | 260 | for (i, probe) in probes.iter().enumerate() { 261 | writeln!( 262 | stream, 263 | " {}: {} {}", 264 | i, 265 | probe.identifier, 266 | probe 267 | .serial_number 268 | .as_ref() 269 | .map(|s| &**s) 270 | .unwrap_or("(no serial number)") 271 | ) 272 | .unwrap(); 273 | } 274 | } 275 | 276 | fn list_channels(channels: &Channels) { 277 | if channels.is_empty() { 278 | println!(" (none)"); 279 | return; 280 | } 281 | 282 | for chan in channels.iter() { 283 | println!( 284 | " {}: {} (buffer size {})", 285 | chan.number(), 286 | chan.name().as_ref().map(|s| &**s).unwrap_or("(no name)"), 287 | chan.buffer_size(), 288 | ); 289 | } 290 | } 291 | 292 | fn stdin_channel() -> Receiver> { 293 | let (tx, rx) = channel(); 294 | 295 | thread::spawn(move || { 296 | let mut buf = [0u8; 1024]; 297 | 298 | loop { 299 | match stdin().read(&mut buf[..]) { 300 | Ok(count) => { 301 | tx.send(buf[..count].to_vec()).unwrap(); 302 | } 303 | Err(err) => { 304 | eprintln!("Error reading from stdin, input disabled: {}", err); 305 | break; 306 | } 307 | } 308 | } 309 | }); 310 | 311 | rx 312 | } 313 | --------------------------------------------------------------------------------