1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! This module contains the implementation for the RTT protocol. It's not meant to be used directly
//! in user code, and therefore mostly undocumented. The module is only public so that it can be
//! accessed from the rtt_init! macro.

use crate::ChannelMode;
use core::cmp::min;
use core::fmt;
use core::ptr;
use core::sync::atomic::{fence, AtomicUsize, Ordering::SeqCst};

// Note: this is zero-initialized in the initialization macro so all zeros must be a valid value
#[repr(C)]
pub struct RttHeader {
    id: [u8; 16],
    max_up_channels: usize,
    max_down_channels: usize,
    // Followed in memory by:
    // up_channels: [Channel; max_up_channels]
    // down_channels: [Channel; down_up_channels]
}

impl RttHeader {
    /// Initializes the control block header.
    ///
    /// # Safety
    ///
    /// The arguments must correspond to the sizes of the arrays that follow the header in memory.
    pub unsafe fn init(&mut self, max_up_channels: usize, max_down_channels: usize) {
        ptr::write_volatile(&mut self.max_up_channels, max_up_channels);
        ptr::write_volatile(&mut self.max_down_channels, max_down_channels);

        // Copy the ID in two parts to avoid having the ID string in memory in full. The ID is
        // copied last to make it less likely an unfinished control block is detected by the host.

        ptr::copy_nonoverlapping(b"SEGG_" as *const u8, self.id.as_mut_ptr(), 5);

        fence(SeqCst);

        ptr::copy_nonoverlapping(
            b"ER RTT\0\0\0\0\0\0" as *const u8,
            self.id.as_mut_ptr().offset(4),
            12,
        );
    }

    pub fn max_up_channels(&self) -> usize {
        self.max_up_channels
    }
}

// Note: this is zero-initialized in the initialization macro so all zeros must be a valid value
#[repr(C)]
pub struct RttChannel {
    name: *const u8,
    buffer: *mut u8,
    size: usize,
    write: AtomicUsize,
    read: AtomicUsize,
    flags: AtomicUsize,
}

impl RttChannel {
    /// Initializes the channel.
    ///
    /// # Safety
    ///
    /// The pointer arguments must point to a valid null-terminated name and writable buffer.
    pub unsafe fn init(&mut self, name: *const u8, mode: ChannelMode, buffer: *mut [u8]) {
        ptr::write_volatile(&mut self.name, name);
        ptr::write_volatile(&mut self.size, (&*buffer).len());
        self.set_mode(mode);

        // Set buffer last as it can be used to detect if the channel has been initialized
        ptr::write_volatile(&mut self.buffer, buffer as *mut u8);
    }

    pub fn is_initialized(&self) -> bool {
        self.buffer != ptr::null_mut()
    }

    pub(crate) fn mode(&self) -> ChannelMode {
        let mode = self.flags.load(SeqCst) & 3;

        if mode <= 2 {
            unsafe { core::mem::transmute(mode) }
        } else {
            ChannelMode::NoBlockSkip
        }
    }

    pub(crate) fn set_mode(&self, mode: ChannelMode) {
        self.flags
            .store((self.flags.load(SeqCst) & !3) | mode as usize, SeqCst);
    }

    // This method should only be called for down channels.
    pub(crate) fn read(&self, mut buf: &mut [u8]) -> usize {
        let (write, mut read) = self.read_pointers();

        let mut total = 0;

        // Read while buffer contains data and output buffer has space (maximum of two iterations)
        while buf.len() > 0 {
            let count = min(self.readable_contiguous(write, read), buf.len());
            if count == 0 {
                break;
            }

            unsafe {
                ptr::copy_nonoverlapping(
                    self.buffer.offset(read as isize),
                    buf.as_mut_ptr(),
                    count,
                );
            }

            total += count;
            read += count;

            if read >= self.size {
                // Wrap around to start
                read = 0;
            }

            buf = &mut buf[count..];
        }

        self.read.store(read, SeqCst);

        total
    }

    /// This method should only be called for up channels.
    pub(crate) fn writer(&self) -> RttWriter<'_> {
        RttWriter {
            chan: self,
            write: self.read_pointers().0,
            total: 0,
            state: WriteState::Writable,
        }
    }

    /// Gets the amount of contiguous data available for reading
    fn readable_contiguous(&self, write: usize, read: usize) -> usize {
        (if read > write {
            self.size - read
        } else {
            write - read
        }) as usize
    }

    fn read_pointers(&self) -> (usize, usize) {
        let write = self.write.load(SeqCst);
        let read = self.read.load(SeqCst);

        if write >= self.size || read >= self.size {
            // Pointers have been corrupted. This doesn't happen in well-behaved programs, so
            // attempt to reset the buffer.

            self.write.store(0, SeqCst);
            self.read.store(0, SeqCst);
            return (0, 0);
        }

        (write, read)
    }
}

/// A cancellable write operation to an RTT channel.
pub(crate) struct RttWriter<'c> {
    chan: &'c RttChannel,
    write: usize,
    total: usize,
    state: WriteState,
}

#[derive(Eq, PartialEq)]
enum WriteState {
    /// Operation can continue
    Writable,

    /// Buffer space ran out but the written data will still be committed
    Full,

    /// The operation failed and won't be committed, or it has already been committed.
    Finished,
}

impl RttWriter<'_> {
    pub fn write(&mut self, buf: &[u8]) {
        self.write_with_mode(self.chan.mode(), buf);
    }

    pub fn write_with_mode(&mut self, mode: ChannelMode, mut buf: &[u8]) {
        while self.state == WriteState::Writable && !buf.is_empty() {
            let count = min(self.writable_contiguous(), buf.len());

            if count == 0 {
                // Buffer is full

                match mode {
                    ChannelMode::NoBlockSkip => {
                        // Mark the entire operation as failed if even one part cannot be written in
                        // full.
                        self.state = WriteState::Finished;
                        return;
                    }

                    ChannelMode::NoBlockTrim => {
                        // If the buffer is full, write as much as possible (note: no return), and
                        // mark the operation as full, which prevents further writes.
                        self.state = WriteState::Full;
                    }

                    ChannelMode::BlockIfFull => {
                        // Commit everything written so far and spin until more can be written
                        self.chan.write.store(self.write, SeqCst);
                        continue;
                    }
                }
            }

            unsafe {
                ptr::copy_nonoverlapping(
                    buf.as_ptr(),
                    self.chan.buffer.offset(self.write as isize),
                    count,
                );
            }

            self.write += count;
            self.total += count;

            if self.write >= self.chan.size {
                // Wrap around to start
                self.write = 0;
            }

            buf = &buf[count..];
        }
    }

    /// Gets the amount of contiguous space available for writing
    fn writable_contiguous(&self) -> usize {
        let read = self.chan.read_pointers().1;

        (if read > self.write {
            read - self.write - 1
        } else if read == 0 {
            self.chan.size - self.write - 1
        } else {
            self.chan.size - self.write
        }) as usize
    }

    pub fn is_failed(&self) -> bool {
        self.state != WriteState::Finished
    }

    pub fn commit(mut self) -> usize {
        self.commit_impl();

        self.total
    }

    fn commit_impl(&mut self) {
        match self.state {
            WriteState::Finished => return,
            WriteState::Full | WriteState::Writable => {
                // Commit the write pointer so the host can see the new data
                self.chan.write.store(self.write, SeqCst);
                self.state = WriteState::Finished;
            }
        }
    }
}

impl Drop for RttWriter<'_> {
    fn drop(&mut self) {
        self.commit_impl();
    }
}

impl fmt::Write for RttWriter<'_> {
    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
        self.write(s.as_bytes());
        Ok(())
    }
}